Reduce number of web requests

This commit is contained in:
pixeltris 2021-06-12 15:22:18 +01:00
parent 9ce9071dbc
commit 4ef55e67a2
6 changed files with 283 additions and 374 deletions

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions // @name TwitchAdSolutions
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.5 // @version 1.6
// @description Multiple solutions for blocking Twitch ads // @description Multiple solutions for blocking Twitch ads
// @author pixeltris // @author pixeltris
// @match *://*.twitch.tv/* // @match *://*.twitch.tv/*
@ -15,8 +15,6 @@
scope.OPT_ROLLING_DEVICE_ID = false; scope.OPT_ROLLING_DEVICE_ID = false;
scope.OPT_MODE_STRIP_AD_SEGMENTS = false; scope.OPT_MODE_STRIP_AD_SEGMENTS = false;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED = false;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST = false;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION = 10000;// In milliseconds
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome'; scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome';
@ -154,24 +152,9 @@
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"'); streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll}); postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
// Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early. // Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early.
if (OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST && streamInfo != null && !streamInfo.NotifyObservedNoAds) { // Deferred to after backup obtained to reduce slowdown. Midrolls are futile.
var noAds = false; if (OPT_MODE_NOTIFY_ADS_WATCHED && !streamInfo.IsMidroll && (streamInfo.BackupFailed || streamInfo.BackupUrl != null)) {
var encodingsM3u8Response = await realFetch(streamInfo.RootM3U8Url); await tryNotifyAdsWatchedM3U8(textStr);
if (encodingsM3u8Response.status === 200) {
var encodingsM3u8 = await encodingsM3u8Response.text();
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
var streamM3u8Response = await realFetch(streamM3u8Url);
if (streamM3u8Response.status == 200) {
noAds = (await tryNotifyAdsWatchedM3U8(await streamM3u8Response.text())) == 1;
console.log('Notify ad watched. Response has ads: ' + !noAds);
}
}
if (streamInfo.NotifyFirstTime == 0) {
streamInfo.NotifyFirstTime = Date.now();
}
if (noAds && !streamInfo.NotifyObservedNoAds && Date.now() >= streamInfo.NotifyFirstTime + OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION) {
streamInfo.NotifyObservedNoAds = true;
}
} }
postMessage({ postMessage({
key: 'UboFoundAdSegment', key: 'UboFoundAdSegment',
@ -353,12 +336,6 @@
streamInfo.BackupRegRes = null; streamInfo.BackupRegRes = null;
streamInfo.IsMidroll = false; streamInfo.IsMidroll = false;
streamInfo.HadAds = false; streamInfo.HadAds = false;
streamInfo.NotifyFirstTime = 0;
streamInfo.NotifyObservedNoAds = false;
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n'); var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) { if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -447,58 +424,63 @@
})); }));
} }
async function tryNotifyAdsWatchedM3U8(streamM3u8) { async function tryNotifyAdsWatchedM3U8(streamM3u8) {
//console.log(streamM3u8); try {
if (!streamM3u8.includes(AD_SIGNIFIER)) { //console.log(streamM3u8);
return 1; if (!streamM3u8.includes(AD_SIGNIFIER)) {
} return 1;
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/); }
if (matches.length > 1) { var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
const attrString = matches[1]; if (matches.length > 1) {
const attr = parseAttributes(attrString); const attrString = matches[1];
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1'); const attr = parseAttributes(attrString);
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0'); var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN']; var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID']; var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID']; var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID']; var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID']; var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase(); var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
const baseData = { var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
stitched: true, const baseData = {
roll_type: rollType, stitched: true,
player_mute: false, roll_type: rollType,
player_volume: 0.5, player_mute: false,
visible: true, player_volume: 0.5,
}; visible: true,
for (let podPosition = 0; podPosition < podLength; podPosition++) { };
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) { for (let podPosition = 0; podPosition < podLength; podPosition++) {
// This is all that's actually required at the moment if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData)); // This is all that's actually required at the moment
} else { await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
const extendedData = { } else {
...baseData, const extendedData = {
ad_id: adId, ...baseData,
ad_position: podPosition, ad_id: adId,
duration: 30, ad_position: podPosition,
creative_id: creativeId, duration: 30,
total_ads: podLength, creative_id: creativeId,
order_id: orderId, total_ads: podLength,
line_item_id: lineItemId, order_id: orderId,
}; line_item_id: lineItemId,
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData)); };
for (let quartile = 0; quartile < 4; quartile++) { await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
await gqlRequest( for (let quartile = 0; quartile < 4; quartile++) {
makeGraphQlPacket('video_ad_quartile_complete', radToken, { await gqlRequest(
...extendedData, makeGraphQlPacket('video_ad_quartile_complete', radToken, {
quartile: quartile + 1, ...extendedData,
}) quartile: quartile + 1,
); })
);
}
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
} }
return 0;
} catch (err) {
console.log(err);
return 0;
} }
return 0;
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = window.fetch;

View File

@ -6,8 +6,6 @@ twitch-videoad.js application/javascript
scope.OPT_ROLLING_DEVICE_ID = true; scope.OPT_ROLLING_DEVICE_ID = true;
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST = false;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION = 10000;// In milliseconds
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = true;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome'; scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome';
@ -145,24 +143,9 @@ twitch-videoad.js application/javascript
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"'); streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll}); postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
// Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early. // Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early.
if (OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST && streamInfo != null && !streamInfo.NotifyObservedNoAds) { // Deferred to after backup obtained to reduce slowdown. Midrolls are futile.
var noAds = false; if (OPT_MODE_NOTIFY_ADS_WATCHED && !streamInfo.IsMidroll && (streamInfo.BackupFailed || streamInfo.BackupUrl != null)) {
var encodingsM3u8Response = await realFetch(streamInfo.RootM3U8Url); await tryNotifyAdsWatchedM3U8(textStr);
if (encodingsM3u8Response.status === 200) {
var encodingsM3u8 = await encodingsM3u8Response.text();
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
var streamM3u8Response = await realFetch(streamM3u8Url);
if (streamM3u8Response.status == 200) {
noAds = (await tryNotifyAdsWatchedM3U8(await streamM3u8Response.text())) == 1;
console.log('Notify ad watched. Response has ads: ' + !noAds);
}
}
if (streamInfo.NotifyFirstTime == 0) {
streamInfo.NotifyFirstTime = Date.now();
}
if (noAds && !streamInfo.NotifyObservedNoAds && Date.now() >= streamInfo.NotifyFirstTime + OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION) {
streamInfo.NotifyObservedNoAds = true;
}
} }
postMessage({ postMessage({
key: 'UboFoundAdSegment', key: 'UboFoundAdSegment',
@ -344,12 +327,6 @@ twitch-videoad.js application/javascript
streamInfo.BackupRegRes = null; streamInfo.BackupRegRes = null;
streamInfo.IsMidroll = false; streamInfo.IsMidroll = false;
streamInfo.HadAds = false; streamInfo.HadAds = false;
streamInfo.NotifyFirstTime = 0;
streamInfo.NotifyObservedNoAds = false;
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n'); var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) { if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -438,58 +415,63 @@ twitch-videoad.js application/javascript
})); }));
} }
async function tryNotifyAdsWatchedM3U8(streamM3u8) { async function tryNotifyAdsWatchedM3U8(streamM3u8) {
//console.log(streamM3u8); try {
if (!streamM3u8.includes(AD_SIGNIFIER)) { //console.log(streamM3u8);
return 1; if (!streamM3u8.includes(AD_SIGNIFIER)) {
} return 1;
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/); }
if (matches.length > 1) { var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
const attrString = matches[1]; if (matches.length > 1) {
const attr = parseAttributes(attrString); const attrString = matches[1];
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1'); const attr = parseAttributes(attrString);
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0'); var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN']; var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID']; var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID']; var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID']; var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID']; var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase(); var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
const baseData = { var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
stitched: true, const baseData = {
roll_type: rollType, stitched: true,
player_mute: false, roll_type: rollType,
player_volume: 0.5, player_mute: false,
visible: true, player_volume: 0.5,
}; visible: true,
for (let podPosition = 0; podPosition < podLength; podPosition++) { };
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) { for (let podPosition = 0; podPosition < podLength; podPosition++) {
// This is all that's actually required at the moment if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData)); // This is all that's actually required at the moment
} else { await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
const extendedData = { } else {
...baseData, const extendedData = {
ad_id: adId, ...baseData,
ad_position: podPosition, ad_id: adId,
duration: 30, ad_position: podPosition,
creative_id: creativeId, duration: 30,
total_ads: podLength, creative_id: creativeId,
order_id: orderId, total_ads: podLength,
line_item_id: lineItemId, order_id: orderId,
}; line_item_id: lineItemId,
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData)); };
for (let quartile = 0; quartile < 4; quartile++) { await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
await gqlRequest( for (let quartile = 0; quartile < 4; quartile++) {
makeGraphQlPacket('video_ad_quartile_complete', radToken, { await gqlRequest(
...extendedData, makeGraphQlPacket('video_ad_quartile_complete', radToken, {
quartile: quartile + 1, ...extendedData,
}) quartile: quartile + 1,
); })
);
}
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
} }
return 0;
} catch (err) {
console.log(err);
return 0;
} }
return 0;
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = window.fetch;

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions (notify-reload) // @name TwitchAdSolutions (notify-reload)
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.5 // @version 1.6
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js // @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
// @description Multiple solutions for blocking Twitch ads (notify-reload) // @description Multiple solutions for blocking Twitch ads (notify-reload)
@ -17,8 +17,6 @@
scope.OPT_ROLLING_DEVICE_ID = true; scope.OPT_ROLLING_DEVICE_ID = true;
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST = false;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION = 10000;// In milliseconds
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = true;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome'; scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome';
@ -156,24 +154,9 @@
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"'); streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll}); postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
// Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early. // Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early.
if (OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST && streamInfo != null && !streamInfo.NotifyObservedNoAds) { // Deferred to after backup obtained to reduce slowdown. Midrolls are futile.
var noAds = false; if (OPT_MODE_NOTIFY_ADS_WATCHED && !streamInfo.IsMidroll && (streamInfo.BackupFailed || streamInfo.BackupUrl != null)) {
var encodingsM3u8Response = await realFetch(streamInfo.RootM3U8Url); await tryNotifyAdsWatchedM3U8(textStr);
if (encodingsM3u8Response.status === 200) {
var encodingsM3u8 = await encodingsM3u8Response.text();
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
var streamM3u8Response = await realFetch(streamM3u8Url);
if (streamM3u8Response.status == 200) {
noAds = (await tryNotifyAdsWatchedM3U8(await streamM3u8Response.text())) == 1;
console.log('Notify ad watched. Response has ads: ' + !noAds);
}
}
if (streamInfo.NotifyFirstTime == 0) {
streamInfo.NotifyFirstTime = Date.now();
}
if (noAds && !streamInfo.NotifyObservedNoAds && Date.now() >= streamInfo.NotifyFirstTime + OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION) {
streamInfo.NotifyObservedNoAds = true;
}
} }
postMessage({ postMessage({
key: 'UboFoundAdSegment', key: 'UboFoundAdSegment',
@ -355,12 +338,6 @@
streamInfo.BackupRegRes = null; streamInfo.BackupRegRes = null;
streamInfo.IsMidroll = false; streamInfo.IsMidroll = false;
streamInfo.HadAds = false; streamInfo.HadAds = false;
streamInfo.NotifyFirstTime = 0;
streamInfo.NotifyObservedNoAds = false;
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n'); var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) { if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -449,58 +426,63 @@
})); }));
} }
async function tryNotifyAdsWatchedM3U8(streamM3u8) { async function tryNotifyAdsWatchedM3U8(streamM3u8) {
//console.log(streamM3u8); try {
if (!streamM3u8.includes(AD_SIGNIFIER)) { //console.log(streamM3u8);
return 1; if (!streamM3u8.includes(AD_SIGNIFIER)) {
} return 1;
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/); }
if (matches.length > 1) { var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
const attrString = matches[1]; if (matches.length > 1) {
const attr = parseAttributes(attrString); const attrString = matches[1];
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1'); const attr = parseAttributes(attrString);
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0'); var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN']; var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID']; var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID']; var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID']; var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID']; var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase(); var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
const baseData = { var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
stitched: true, const baseData = {
roll_type: rollType, stitched: true,
player_mute: false, roll_type: rollType,
player_volume: 0.5, player_mute: false,
visible: true, player_volume: 0.5,
}; visible: true,
for (let podPosition = 0; podPosition < podLength; podPosition++) { };
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) { for (let podPosition = 0; podPosition < podLength; podPosition++) {
// This is all that's actually required at the moment if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData)); // This is all that's actually required at the moment
} else { await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
const extendedData = { } else {
...baseData, const extendedData = {
ad_id: adId, ...baseData,
ad_position: podPosition, ad_id: adId,
duration: 30, ad_position: podPosition,
creative_id: creativeId, duration: 30,
total_ads: podLength, creative_id: creativeId,
order_id: orderId, total_ads: podLength,
line_item_id: lineItemId, order_id: orderId,
}; line_item_id: lineItemId,
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData)); };
for (let quartile = 0; quartile < 4; quartile++) { await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
await gqlRequest( for (let quartile = 0; quartile < 4; quartile++) {
makeGraphQlPacket('video_ad_quartile_complete', radToken, { await gqlRequest(
...extendedData, makeGraphQlPacket('video_ad_quartile_complete', radToken, {
quartile: quartile + 1, ...extendedData,
}) quartile: quartile + 1,
); })
);
}
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
} }
return 0;
} catch (err) {
console.log(err);
return 0;
} }
return 0;
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = window.fetch;

View File

@ -6,8 +6,6 @@ twitch-videoad.js application/javascript
scope.OPT_ROLLING_DEVICE_ID = true; scope.OPT_ROLLING_DEVICE_ID = true;
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION = 10000;// In milliseconds
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome'; scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome';
@ -145,24 +143,9 @@ twitch-videoad.js application/javascript
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"'); streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll}); postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
// Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early. // Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early.
if (OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST && streamInfo != null && !streamInfo.NotifyObservedNoAds) { // Deferred to after backup obtained to reduce slowdown. Midrolls are futile.
var noAds = false; if (OPT_MODE_NOTIFY_ADS_WATCHED && !streamInfo.IsMidroll && (streamInfo.BackupFailed || streamInfo.BackupUrl != null)) {
var encodingsM3u8Response = await realFetch(streamInfo.RootM3U8Url); await tryNotifyAdsWatchedM3U8(textStr);
if (encodingsM3u8Response.status === 200) {
var encodingsM3u8 = await encodingsM3u8Response.text();
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
var streamM3u8Response = await realFetch(streamM3u8Url);
if (streamM3u8Response.status == 200) {
noAds = (await tryNotifyAdsWatchedM3U8(await streamM3u8Response.text())) == 1;
console.log('Notify ad watched. Response has ads: ' + !noAds);
}
}
if (streamInfo.NotifyFirstTime == 0) {
streamInfo.NotifyFirstTime = Date.now();
}
if (noAds && !streamInfo.NotifyObservedNoAds && Date.now() >= streamInfo.NotifyFirstTime + OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION) {
streamInfo.NotifyObservedNoAds = true;
}
} }
postMessage({ postMessage({
key: 'UboFoundAdSegment', key: 'UboFoundAdSegment',
@ -344,12 +327,6 @@ twitch-videoad.js application/javascript
streamInfo.BackupRegRes = null; streamInfo.BackupRegRes = null;
streamInfo.IsMidroll = false; streamInfo.IsMidroll = false;
streamInfo.HadAds = false; streamInfo.HadAds = false;
streamInfo.NotifyFirstTime = 0;
streamInfo.NotifyObservedNoAds = false;
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n'); var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) { if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -438,58 +415,63 @@ twitch-videoad.js application/javascript
})); }));
} }
async function tryNotifyAdsWatchedM3U8(streamM3u8) { async function tryNotifyAdsWatchedM3U8(streamM3u8) {
//console.log(streamM3u8); try {
if (!streamM3u8.includes(AD_SIGNIFIER)) { //console.log(streamM3u8);
return 1; if (!streamM3u8.includes(AD_SIGNIFIER)) {
} return 1;
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/); }
if (matches.length > 1) { var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
const attrString = matches[1]; if (matches.length > 1) {
const attr = parseAttributes(attrString); const attrString = matches[1];
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1'); const attr = parseAttributes(attrString);
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0'); var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN']; var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID']; var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID']; var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID']; var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID']; var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase(); var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
const baseData = { var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
stitched: true, const baseData = {
roll_type: rollType, stitched: true,
player_mute: false, roll_type: rollType,
player_volume: 0.5, player_mute: false,
visible: true, player_volume: 0.5,
}; visible: true,
for (let podPosition = 0; podPosition < podLength; podPosition++) { };
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) { for (let podPosition = 0; podPosition < podLength; podPosition++) {
// This is all that's actually required at the moment if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData)); // This is all that's actually required at the moment
} else { await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
const extendedData = { } else {
...baseData, const extendedData = {
ad_id: adId, ...baseData,
ad_position: podPosition, ad_id: adId,
duration: 30, ad_position: podPosition,
creative_id: creativeId, duration: 30,
total_ads: podLength, creative_id: creativeId,
order_id: orderId, total_ads: podLength,
line_item_id: lineItemId, order_id: orderId,
}; line_item_id: lineItemId,
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData)); };
for (let quartile = 0; quartile < 4; quartile++) { await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
await gqlRequest( for (let quartile = 0; quartile < 4; quartile++) {
makeGraphQlPacket('video_ad_quartile_complete', radToken, { await gqlRequest(
...extendedData, makeGraphQlPacket('video_ad_quartile_complete', radToken, {
quartile: quartile + 1, ...extendedData,
}) quartile: quartile + 1,
); })
);
}
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
} }
return 0;
} catch (err) {
console.log(err);
return 0;
} }
return 0;
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = window.fetch;

View File

@ -1,5 +1,4 @@
OPT_ROLLING_DEVICE_ID true OPT_ROLLING_DEVICE_ID true
OPT_MODE_STRIP_AD_SEGMENTS true OPT_MODE_STRIP_AD_SEGMENTS true
OPT_MODE_NOTIFY_ADS_WATCHED true OPT_MODE_NOTIFY_ADS_WATCHED true
OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST true
OPT_ACCESS_TOKEN_PLAYER_TYPE 'site' OPT_ACCESS_TOKEN_PLAYER_TYPE 'site'

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions (notify-strip) // @name TwitchAdSolutions (notify-strip)
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.5 // @version 1.6
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js // @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
// @description Multiple solutions for blocking Twitch ads (notify-strip) // @description Multiple solutions for blocking Twitch ads (notify-strip)
@ -17,8 +17,6 @@
scope.OPT_ROLLING_DEVICE_ID = true; scope.OPT_ROLLING_DEVICE_ID = true;
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION = 10000;// In milliseconds
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED_RELOAD_PLAYER_ON_AD_SEGMENT = false;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome'; scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture';//'picture-by-picture';'thunderdome';
@ -156,24 +154,9 @@
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"'); streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll}); postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
// Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early. // Notify ads "watched" TODO: Keep crafting these requests even after ad tags are gone as sometimes it stops too early.
if (OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST && streamInfo != null && !streamInfo.NotifyObservedNoAds) { // Deferred to after backup obtained to reduce slowdown. Midrolls are futile.
var noAds = false; if (OPT_MODE_NOTIFY_ADS_WATCHED && !streamInfo.IsMidroll && (streamInfo.BackupFailed || streamInfo.BackupUrl != null)) {
var encodingsM3u8Response = await realFetch(streamInfo.RootM3U8Url); await tryNotifyAdsWatchedM3U8(textStr);
if (encodingsM3u8Response.status === 200) {
var encodingsM3u8 = await encodingsM3u8Response.text();
var streamM3u8Url = encodingsM3u8.match(/^https:.*\.m3u8$/m)[0];
var streamM3u8Response = await realFetch(streamM3u8Url);
if (streamM3u8Response.status == 200) {
noAds = (await tryNotifyAdsWatchedM3U8(await streamM3u8Response.text())) == 1;
console.log('Notify ad watched. Response has ads: ' + !noAds);
}
}
if (streamInfo.NotifyFirstTime == 0) {
streamInfo.NotifyFirstTime = Date.now();
}
if (noAds && !streamInfo.NotifyObservedNoAds && Date.now() >= streamInfo.NotifyFirstTime + OPT_MODE_NOTIFY_ADS_WATCHED_PERSIST_EXPECTED_DURATION) {
streamInfo.NotifyObservedNoAds = true;
}
} }
postMessage({ postMessage({
key: 'UboFoundAdSegment', key: 'UboFoundAdSegment',
@ -355,12 +338,6 @@
streamInfo.BackupRegRes = null; streamInfo.BackupRegRes = null;
streamInfo.IsMidroll = false; streamInfo.IsMidroll = false;
streamInfo.HadAds = false; streamInfo.HadAds = false;
streamInfo.NotifyFirstTime = 0;
streamInfo.NotifyObservedNoAds = false;
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n'); var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) { if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -449,58 +426,63 @@
})); }));
} }
async function tryNotifyAdsWatchedM3U8(streamM3u8) { async function tryNotifyAdsWatchedM3U8(streamM3u8) {
//console.log(streamM3u8); try {
if (!streamM3u8.includes(AD_SIGNIFIER)) { //console.log(streamM3u8);
return 1; if (!streamM3u8.includes(AD_SIGNIFIER)) {
} return 1;
var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/); }
if (matches.length > 1) { var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^\n]+)\n/);
const attrString = matches[1]; if (matches.length > 1) {
const attr = parseAttributes(attrString); const attrString = matches[1];
var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1'); const attr = parseAttributes(attrString);
var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0'); var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN']; var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID']; var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
var orderId = attr['X-TV-TWITCH-AD-ORDER-ID']; var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID']; var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID']; var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase(); var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
const baseData = { var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
stitched: true, const baseData = {
roll_type: rollType, stitched: true,
player_mute: false, roll_type: rollType,
player_volume: 0.5, player_mute: false,
visible: true, player_volume: 0.5,
}; visible: true,
for (let podPosition = 0; podPosition < podLength; podPosition++) { };
if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) { for (let podPosition = 0; podPosition < podLength; podPosition++) {
// This is all that's actually required at the moment if (OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS) {
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData)); // This is all that's actually required at the moment
} else { await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
const extendedData = { } else {
...baseData, const extendedData = {
ad_id: adId, ...baseData,
ad_position: podPosition, ad_id: adId,
duration: 30, ad_position: podPosition,
creative_id: creativeId, duration: 30,
total_ads: podLength, creative_id: creativeId,
order_id: orderId, total_ads: podLength,
line_item_id: lineItemId, order_id: orderId,
}; line_item_id: lineItemId,
await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData)); };
for (let quartile = 0; quartile < 4; quartile++) { await gqlRequest(makeGraphQlPacket('video_ad_impression', radToken, extendedData));
await gqlRequest( for (let quartile = 0; quartile < 4; quartile++) {
makeGraphQlPacket('video_ad_quartile_complete', radToken, { await gqlRequest(
...extendedData, makeGraphQlPacket('video_ad_quartile_complete', radToken, {
quartile: quartile + 1, ...extendedData,
}) quartile: quartile + 1,
); })
);
}
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
await gqlRequest(makeGraphQlPacket('video_ad_pod_complete', radToken, baseData));
} }
} }
return 0;
} catch (err) {
console.log(err);
return 0;
} }
return 0;
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = window.fetch;