HaGeZi Most Abused TLDs to NextDNS

This script will check and add TLDs from HaGeZi Most Abused TLDs (Aggressive) list and allowlist to your NextDNS profile when you visit the profile page. The process can be checked in the browser console.

// ==UserScript==
// @name        HaGeZi Most Abused TLDs to NextDNS
// @namespace   vietthe.dev
// @match       https://my.nextdns.io/*
// @grant       none
// @version     1.0.4
// @license     MIT
// @author      Salad
// @compatible  firefox Violentmonkey
// @compatible  firefox Tampermonkey
// @compatible  firefox FireMonkey
// @compatible  chrome Violentmonkey
// @compatible  chrome Tampermonkey
// @compatible  opera Violentmonkey
// @compatible  opera Tampermonkey
// @compatible  safari Stay
// @compatible  edge Violentmonkey
// @compatible  edge Tampermonkey
// @compatible  brave Violentmonkey
// @compatible  brave Tampermonkey
// @description This script will check and add TLDs from HaGeZi Most Abused TLDs (Aggressive) list and allowlist to your NextDNS profile when you visit the profile page. The process can be checked in the browser console.
// @icon         data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAFOklEQVRogbXaXaicRxkH8N/sJmnSlOZIDC1CJRCseETFK72qFBpFvJQUpAptFdorL0pKkV540AvxQm8sokdsY6ISjNVCxSBUiSLRoq0XYpEqGGmbUGxPTmKO52N35/Fi93zsnvdrPzIw7Owzzzzz/z/zzLwz77zJzUgL0TLvsDV3oKfnigddk1LMuqvWrA2CY+6x7qzkT3hR26IfmL8ZXaWZWzwTn8ZTuENIAiEL/xQ+4+H08iy7mx2Bp+OIPb4geVI4aDNYYpD75beFx6x4zhfT9Vl0Oz2BZ2JO23E8LNyLW0rA93NyXfYLnHLI792fVqfpfnICp+Og5H7hIXxIuL0UdHH5LbyoZ9H7/NK9qTsJjAoCkTzvgGWHtOyT7ZW8U/JB4T7huDDXEOxweff/K8J5PRdkf9N2TdbBumXXLKS18Qn8KD6BE/iA7DDmMCe0xwZcDX5YRkd2VVgW/iP7M844mV5qTuCH8ajkq8JhMdAZ7qQZ6NyAyKjOqDzLuIzPeTxdqCdwOj4ieU5yZ4F3xvfwaF2ZrE6e/V1y3BPp9Z1whx9kC7FH8knhiDzwQoz8VpXr6spkVfLt/G49x42kYQLz9uOY0N5iv2l8NBeBHRd8kZ2iNj2EA8IxJ6K9E/KeEULtgWI/jRMelMfzOOFSXp9kt3qH1oBSAYEVaUs27iozC/BldrZz27uG5+0wgX2SDa3K1WMWICcB369vu1pFYFWStG7aSjJuKO2uS94zhHjXHCAGBMYBT/16Pj34PralqhFYk9wibc3+nUaqQE4aElGgU01q13NrmMB+oTswebNCosoJuUKnn7NLVQQ2hKQ781AZZ7Sq7CRdR7c0Cgi0ZV0bMwdfpLMpG8/O+g6tAgIHdF23OnWoVOnsBNpk1LbbBFarCSzrym5IDUDWdTwt+FGd/q70hoVUQWBJx5yrQtg+kE8Wy+NO5HqdDWHZSBrZjaYsW5L1SjdWUSJvstuMCXX65Q3J26MEdj/IuCJbE25r7LEmYVVmp4n9ft2acKWeQM9lYVW4bQzjTSZhuW4z+arwRj2BrktaVoQjM/d2k/qyNsmSjsujcHe/WvyXN2SvV56OymJ4+ABSd8Iqb1vc30WLqVNPYDF1ZL8unWDTgi+byGWTd1t+fhfWQgLQ8/OhlajOm+OuMKOAy+TbsiXrftOcQPZX2Uu1Ht+Zy4e+2NtFI1W+hJ51qvjlVjGB/vPgO3olwKYFWQV+d7iuyE4X4iwlAD0vyF6p9FLR6JSF06i8rrydL2j5x/gE9npTeFbWaTxJm8qL7BTpcEN41l27txCbqfrt9JfibuFXsqMMDNPsuVCmX9Reaf1FLZ9yKpUSqL5i+lp6Vfb1rVEo8lLRsNeF0GjbYof8FyerwNcTgGXfF87I8kThUka8uryq5ytOpz/UwasnsJg6woLsd7Ugy1eScSZyFn5i3fdqsRnnhuax+KiuU8J7G8dzVXwXl0O4KDzgx+nfTWA1v2b9Zvqj8Ijwl6GVZLpQGfY854WHmoJnkjuyR2Jey7eFe+w8tTGp16EnnNXxhHNp15a5Ko1/0f3d9IrwgOynspXa1aR+tVmSfcucR8cFzzS3lCfigDmfHYTVh0XBO9Wd/43I6Ai/xVPu9vzoYb1pmu6eeCFaXnOX8HnZSQZ3C1XA+79vCl+238884y0m/4Zidjf1D8ZR2ZP4uHAn9o2A/p/+kfCcjm84l5Zm0e1sv5U4EW0Hzeu5T/YxvF/o4mXZBXu94IxL03h8NM3+Yw/6oXXJ7TYckmW3Wva0G7MEvpn+DxZzjNuWWt/KAAAAAElFTkSuQmCC
// ==/UserScript==

/* jshint esversion:2020 */

(async () => {
  const apiHost = "https://api.nextdns.io"
  const invalidTlds = ["non"]

  const sleep = (ms = 500) => new Promise((resolve) => setTimeout(() => resolve(), ms))

  const toHex = (text) => {
    let hex = ''

    for (let i = 0; i < text.length; i++) {
      const charCode = text.charCodeAt(i)
      const hexCode = charCode.toString(16).padStart(2, '0')

      hex += hexCode
    }

    return hex;
  }

  const callApi = (path, options) => fetch(`${apiHost}${path}`, {
    headers: {
      "Content-Type": "application/json",
      ...options?.headers,
    },
    mode: "cors",
    credentials: "include",
    ...options,
  })

  const getSecurity = (profileId) => callApi(`/profiles/${profileId}/security`).then((res) => res.json())

  const getAllowlist = (profileId) => callApi(`/profiles/${profileId}/allowlist`).then((res) => res.json())

  const getAbusedTlds = async () => {
    const content = await fetch("https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/spam-tlds-adblock-aggressive.txt").then(res => res.text())
    const tlds = content.trim().match(/^\|\|(xn--)?\w+\^$/gm).map((e) => e.slice(2, -1))

    return tlds
  }

  const getTldAllowlist = async () => {
    const content = await fetch("https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/spam-tlds-adblock-allow.txt").then(res => res.text())
    const domains = content.trim().split("\n").map((e) => e.slice(4, -1))

    return domains
  }

  const addTld = (profileId, tld) => callApi(`/profiles/${profileId}/security/tlds`, {
    method: "POST",
    body: JSON.stringify({ id: tld }),
  })

  const removeTld = (profileId, tld) => callApi(`/profiles/${profileId}/security/tlds/hex:${toHex(tld)}`, {
    method: "DELETE",
  })

  const allowlistDomain = (profileId, domain) => callApi(`/profiles/${profileId}/allowlist`, {
    method: "POST",
    body: JSON.stringify({ active: true, id: domain }),
  })

  const run = async (profileId) => {
    const currentTlds = (await getSecurity(profileId)).data.tlds.map(({ id }) => id)
    const currentAllowlist = (await getAllowlist(profileId)).data.map(({ id }) => id)
    const tlds = await getAbusedTlds(profileId)
    const tldAllowlist = await getTldAllowlist(profileId)
    const tldsToAdd = tlds.filter((e) => !currentTlds.includes(e) && !invalidTlds.includes(e))
    const domainsToAllowlist = tldAllowlist.filter((e) => !currentAllowlist.includes(e))
    let index = 1

    console.info({ currentTlds, currentAllowlist, tldsToAdd, domainsToAllowlist })

    for (const tld of tldsToAdd) {
      console.info(`Adding .${tld} to TLD blocklist (${index}/${tldsToAdd.length})`)
      await addTld(profileId, tld)
      await sleep()
      index++
    }

    index = 1

    for (const domain of domainsToAllowlist) {
      console.info(`Adding ${domain} to allowlist (${index}/${domainsToAllowlist.length})`)
      await allowlistDomain(profileId, domain)
      await sleep()
      index++
    }
  }

  const onUrlChange = async () => {
    if (location.pathname.split("/").length < 3) return

    console.info("HaGeZi Most Abused TLDs to NextDNS is running...")

    await run(location.pathname.split("/")[1])

    console.info("Done.")
  }

  navigation.addEventListener("navigatesuccess", onUrlChange)

  onUrlChange()
})()