Skip to content

Instantly share code, notes, and snippets.

@rtfpessoa
Last active January 14, 2024 02:05
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save rtfpessoa/7fdd3d4121ee4acafe56e8425154888a to your computer and use it in GitHub Desktop.
Save rtfpessoa/7fdd3d4121ee4acafe56e8425154888a to your computer and use it in GitHub Desktop.
nextdns.io Block Youtube Ads
// ID of the config, e.g. A1234BCD.
const configID = "A1234BCD";
// API key, found at the bottom of your account page in https://my.nextdns.io/account
const APIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
// Mark true or false. If true, failed links will be retried 3 times at progressively increasing intervals.
// If false, failed links will not be retried.
const retryFailedLinks = true;
// Time delay between requests in milliseconds.
// 800 seems to not give any errors but is rather slow while anything faster will give errors (in limited testing).
// If you want to go lower, it is recommended that "retryFailedLinks" is true
const timeDelay = 200;
const ignoreDomainsSet = new Set([
"clients.l.google.com",
"clients1.google.com",
"clients2.google.com",
"clients3.google.com",
"clients4.google.com",
"clients5.google.com",
"clients6.google.com",
"akamaiedge.net",
]);
const youtubeAdDomainsSet = new Set();
const existingLinkSet = new Set();
const fetchEwprattenDomains = async () => {
const response = await fetch(
"https://raw.githubusercontent.com/Ewpratten/youtube_ad_blocklist/master/blocklist.txt"
);
const text = await response.text();
text.split("\n").forEach((line) => youtubeAdDomainsSet.add(line));
return;
};
const fetchkboghdadyDomains = async () => {
const response = await fetch(
"https://raw.githubusercontent.com/kboghdady/youTube_ads_4_pi-hole/master/youtubelist.txt"
);
const text = await response.text();
text.split("\n").forEach((line) => youtubeAdDomainsSet.add(line));
return;
};
const fetchGoodbyeAdsDomains = async () => {
const response = await fetch(
"https://raw.githubusercontent.com/jerryn70/GoodbyeAds/master/Formats/GoodbyeAds-YouTube-AdBlock-Filter.txt"
);
const text = await response.text();
text.split("\n").forEach((line) => {
if (line.startsWith("||") && line.endsWith("^")) {
const domain = line.substring(2, line.length - 1);
youtubeAdDomainsSet.add(domain);
}
});
return;
};
const fetchExistingLinks = async () => {
const response = await fetch(
`https://api.nextdns.io/profiles/${configID}/denylist`,
{
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json",
"X-Api-Key": JSON.stringify(APIKey),
},
method: "GET",
mode: "cors",
credentials: "include",
}
);
const domains = await response.json();
domains.data.map((domain) => {
existingLinkSet.add(domain.id);
});
return;
};
function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
const blockDomain = async (domain) =>
fetch(`https://api.nextdns.io/profiles/${configID}/denylist`, {
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json",
"X-Api-Key": JSON.stringify(APIKey),
},
body: JSON.stringify({ id: domain, active: true }),
method: "POST",
mode: "cors",
credentials: "include",
}).then((response) => {
if (response.ok) {
return response.json;
}
throw "Failed to block domain";
});
const blockDomains = async (domains, delay = timeDelay) => {
console.log(`Preparing to block ${domains.length} domains`);
const failedLinksSet = new Set();
for (let idx = 0; idx < domains.length; idx++) {
const domain = domains[idx];
if (ignoreDomainsSet.has(domain)) {
console.log(`Ignoring ${domain} ${idx + 1}/${domains.length}`);
continue;
}
if (existingLinkSet.has(domain)) {
console.log(`Skipping ${domain} ${idx + 1}/${domains.length}`);
continue;
}
try {
console.log(`Blocking ${domain} ${idx + 1}/${domains.length}`);
await blockDomain(domain);
} catch (error) {
console.error(error);
failedLinksSet.add(domain);
}
await sleep(delay);
}
return Array.from(failedLinksSet);
};
const stringToHex = (str) =>
str
.split("")
.map((c) => c.charCodeAt(0).toString(16).padStart(2, "0"))
.join("");
const unblockDomain = async (domain) =>
fetch(
`https://api.nextdns.io/profiles/${configID}/denylist/hex:${stringToHex(
domain
)}`,
{
headers: {
accept: "application/json, text/plain, */*",
"content-type": "application/json",
"X-Api-Key": JSON.stringify(APIKey),
},
body: null,
method: "DELETE",
mode: "cors",
credentials: "include",
}
);
const unblockDomains = async (domains, delay = timeDelay) => {
console.log(`Preparing to unblock ${domains.length} domains`);
for (let idx = 0; idx < domains.length; idx++) {
try {
console.log(`Unblocking ${domains[idx]} ${idx}/${domains.length}`);
await unblockDomain(domains[idx]);
} catch (error) {
console.error(error);
}
await sleep(delay);
}
};
const retry = async (fn, initialInput, retries, delayMs) => {
let attempt = 0;
let delay = timeDelay;
let nextInput = initialInput;
while (true) {
nextInput = await fn(nextInput, delay);
if (nextInput.size == 0) {
return;
}
console.log(`Retry ${attempt}/${retries} failed. Retrying again...`);
if (attempt++ > retries) {
console.error("Failed domains", nextInput);
throw "Retries exceeded";
}
delay += delayMs * attempt;
}
};
const run = async () => {
console.log(`Downloading domains to block ...`);
await fetchEwprattenDomains();
await fetchkboghdadyDomains();
await fetchGoodbyeAdsDomains();
await fetchExistingLinks();
const youtubeAdDomains = Array.from(youtubeAdDomainsSet);
const retries = retryFailedLinks ? 3 : 0;
await retry(blockDomains, youtubeAdDomains, retries, 100);
// await unblockDomains(Array.from(existingLinkSet));
console.log("Have fun!");
};
run();
@ddulic
Copy link

ddulic commented Dec 18, 2022

Sadly, doesn’t seem to work for the iOS, iPadOS and tvOS apps.

Copy link

ghost commented Dec 18, 2022

Sadly, doesn’t seem to work for the iOS, iPadOS and tvOS apps.

no need to use those awful OSes. use Windows or Android only.

@ddulic
Copy link

ddulic commented Dec 18, 2022

Sadly, doesn’t seem to work for the iOS, iPadOS and tvOS apps.

no need to use those awful OSes. use Windows or Android only.

Thank you for your unwanted opinion :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment