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.5
// @license     MIT
// @author      Salad
// @compatible  firefox Violentmonkey
// @compatible  firefox Tampermonkey
// @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 () => {
  class Utility {
    static sleep(ms = 500) {
      return new Promise((resolve) => setTimeout(() => resolve(), ms))
    }

    static 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
    }
  }

  class Helper {
    static getProfileId() {
      return location.pathname.split("/")[1]
    }

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

      return tlds
    }

    static async getAggressiveAbusedTlds() {
      const content = await fetch("https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/spam-tlds-onlydomains.txt").then((res) => res.text())
      const tlds = content.trim().match(/^\|\|(xn--)?\w+\^$/gm)

      return tlds
    }

    static async getTldAllowlist() {
      const content = await fetch("https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/spam-tlds-allow-onlydomains.txt").then(res => res.text())
      const domains = content.trim().split("\n")

      return domains
    }
  }

  class NextApi {
    apiHost = "https://api.nextdns.io"

    constructor(profileId) {
      this.profileId = profileId
    }

    request(path, options) {
      return fetch(`${this.apiHost}${path}`, {
        headers: {
        "Content-Type": "application/json",
        ...options?.headers,
        },
        mode: "cors",
        credentials: "include",
        ...options,
      })
    }

    getSecurity() {
      return this.request(`/profiles/${this.profileId}/security`).then((res) => res.json())
    }

    getAllowlist() {
      return this.request(`/profiles/${this.profileId}/allowlist`).then((res) => res.json())
    }

    addTld(tld) {
      return this.request(`/profiles/${this.profileId}/security/tlds`, {
        method: "POST",
        body: JSON.stringify({ id: tld }),
      })
    }

    removeTld(tld) {
      return this.request(`/profiles/${this.profileId}/security/tlds/hex:${Utility.toHex(tld)}`, {
        method: "DELETE",
      })
    }

    allowlistDomain(domain) {
      return this.request(`/profiles/${this.profileId}/allowlist`, {
        method: "POST",
        body: JSON.stringify({ active: true, id: domain }),
      })
    }
  }

  const run = async () => {
    const profileId = Helper.getProfileId()
    const nextApi = new NextApi(profileId)
    const invalidTlds = ["non"]
    const currentTlds = (await nextApi.getSecurity()).data.tlds.map(({ id }) => id)
    const currentAllowlist = (await nextApi.getAllowlist()).data.map(({ id }) => id)
    const tlds = await Helper.getAbusedTlds()
    const tldAllowlist = await Helper.getTldAllowlist()
    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 nextApi.addTld(tld)
      await Utility.sleep()
      index++
    }

    index = 1

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

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

    if (!profileIdExists) return

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

    await run()

    console.info("Done.")
  }

  navigation.addEventListener("navigatesuccess", onUrlChange)

  onUrlChange()
})()