// ==UserScript== // @name HaGeZi to NextDNS // @namespace vietthe.dev // @match https://my.nextdns.io/* // @grant GM_addStyle // @version 1.1.2 // @license MIT // @author vietthedev // @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 A userscript that allows you to fetch HaGeZi lists and add them to your NextDNS allowlist and denylist. // @description:en A userscript that allows you to fetch HaGeZi lists and add them to your NextDNS allowlist and denylist. // @description:vi Userscript giúp bạn nhập các danh sách HaGeZi vào danh sách cho phép và danh sách chặn của NextDNS. // @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 // @homepageURL https://github.com/vietthedev/hagezi-to-nextdns // @supportURL https://github.com/vietthedev/hagezi-to-nextdns/issues // ==/UserScript== /* jshint esversion:2020 */ (async (window) => { const punycode = await import("https://esm.run/punycode"); const lang = { en: { hidePanel: "Hide Panel", showPanel: "Show Panel", numberOfBlockedTlds: "Number of blocked TLDs", numberOfDeniedDomains: "Number of denied domains", numberOfAllowedDomains: "Number of allowed domains", importHageziTlds: "Import HaGeZi TLDs", removeHageziTlds: "Remove HaGeZi TLDs", importHageziTldsAggressive: "Import HaGeZi TLDs (Aggressive)", removeHageziTldsAggressive: "Remove HaGeZi TLDs (Aggressive)", importHageziTldAllowlist: "Import HaGeZi TLD allowlist", removeHageziTldAllowlist: "Remove HaGeZi TLD allowlist", importErrorTrackers: "Import error trackers", removeErrorTrackers: "Remove error trackers", importCustomList: "Import custom list", removeCustomList: "Remove custom list", adding: "Adding", removing: "Removing", done: "Done!", expand: "Expand", collapse: "Collapse", chooseAListToProceed: "Choose a list to proceed:\n1. Allowlist\n2. Denylist", areYouSureYouWantToProceed: "Are you sure you want to proceed?", allowlist: "Allowlist", denylist: "Denylist", enterTheLink: "Enter the link to the list in wildcard asterisk or wildcard domain format:", }, vi: { hidePanel: "Ẩn Bảng", showPanel: "Hiện Bảng", numberOfBlockedTlds: "Số lượng TLD đã chặn", numberOfDeniedDomains: "Số lượng tên miền đã chặn", numberOfAllowedDomains: "Số lượng tên miền đã cho phép", importHageziTlds: "Nhập TLD HaGeZi", removeHageziTlds: "Xoá TLD HaGeZi", importHageziTldsAggressive: "Nhập TLD HaGeZi (phiên bản mạnh hơn)", removeHageziTldsAggressive: "Xoá TLD HaGeZi (phiên bản mạnh hơn)", importHageziTldAllowlist: "Nhập danh sách cho phép HaGeZi TLD", removeHageziTldAllowlist: "Xoá danh sách cho phép HaGeZi TLD", importErrorTrackers: "Nhập trình theo dõi lỗi", removeErrorTrackers: "Xoá trình theo dõi lỗi", importCustomList: "Nhập danh sách tuỳ chọn", removeCustomList: "Xoá danh sách tuỳ chọn", adding: "Đang thêm", removing: "Đang xoá", done: "Xong!", expand: "Mở rộng", collapse: "Thu gọn", chooseAListToProceed: "Chọn danh sách để tiến hành:\n1. Danh sách cho phép\n2. Danh sách chặn", areYouSureYouWantToProceed: "Bạn có chắc muốn tiến hành?", allowlist: "Danh sách cho phép", denylist: "Danh sách chặn", enterTheLink: 'Nhập đường dẫn tới danh sách tên miền ở định dạng có hoặc không có dấu "*":', }, }[window.navigator.language === "vi" ? "vi" : "en"]; const ListType = { Allowlist: "1", Denylist: "2", }; const Utility = { sleep: (ms = 500) => new Promise((resolve) => setTimeout(() => resolve(), ms)), 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 Helper = { getProfileId: () => location.pathname.split("/")[1], profileIdExists: () => location.pathname.split("/").length >= 3, confirmProceed: () => window.confirm(lang.areYouSureYouWantToProceed), promptListType: () => { let answer; while (answer !== null && !Object.values(ListType).includes(answer)) { answer = window.prompt(lang.chooseAListToProceed); } return answer; }, promptUrl: () => { const pattern = /^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/[a-zA-Z0-9]+\.[^\s]{2,}|[a-zA-Z0-9]+\.[^\s]{2,})$/; let answer; while (answer !== null && !pattern.test(answer)) { answer = window.prompt(lang.enterTheLink); } return answer; }, getAdblockList: async (url, isTldList = false) => { const content = await fetch(url).then((res) => res.text()); const matches = content .trim() .matchAll(/^(\|\|)?(?(xn--)?[\w.-]+)(\^)?$/gm); const domains = new Set(); for (const match of matches) { if (isTldList && match.groups.domain.includes(".")) continue; domains.add(match.groups.domain); } return domains; }, getWildcardList: async (url, isTldList = false) => { const content = await fetch(url).then((res) => res.text()); const matches = content.trim().matchAll(/^(\*\.)*(?[\w.-]+)$/gm); const domains = new Set(); for (const match of matches) { if (isTldList && match.groups.domain.includes(".")) continue; domains.add(match.groups.domain); } return domains; }, getAbusedTlds: () => Helper.getAdblockList( "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/spam-tlds-adblock.txt", true, ), getAbusedTldsAggressive: () => Helper.getWildcardList( "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/wildcard/spam-tlds-onlydomains.txt", true, ), getTldAllowlist: () => Helper.getWildcardList( "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/wildcard/spam-tlds-allow-onlydomains.txt", ), getErrorTrackerList: () => Helper.getWildcardList( "https://cdn.jsdelivr.net/gh/vietthedev/hagezi-to-nextdns@latest/resources/error-trackers-onlydomains.txt", ), }; class NextApi { apiHost = "https://api.nextdns.io"; constructor(profileId) { this.profileId = profileId; } async request(path, options) { const response = await fetch(`${this.apiHost}${path}`, { headers: { "Content-Type": "application/json", ...options?.headers, }, mode: "cors", credentials: "include", ...options, }); if (!response.ok) { throw new Error(response.statusText); } return response; } async getSecurity() { const res = await this.request(`/profiles/${this.profileId}/security`); return await res.json(); } async getDenylist() { const res = await this.request(`/profiles/${this.profileId}/denylist`); return await res.json(); } async getAllowlist() { const res = await this.request(`/profiles/${this.profileId}/allowlist`); return await 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", }, ); } denylistDomain(domain) { return this.request(`/profiles/${this.profileId}/denylist`, { method: "POST", body: JSON.stringify({ active: true, id: domain }), }); } allowlistDomain(domain) { return this.request(`/profiles/${this.profileId}/allowlist`, { method: "POST", body: JSON.stringify({ active: true, id: domain }), }); } removeDeniedDomain(domain) { return this.request( `/profiles/${this.profileId}/denylist/hex:${Utility.toHex(domain)}`, { method: "DELETE", }, ); } removeAllowedDomain(domain) { return this.request( `/profiles/${this.profileId}/allowlist/hex:${Utility.toHex(domain)}`, { method: "DELETE", }, ); } } const arrowDownHtml = ``; const arrowUpHtml = ``; const hidePanel = () => { const panelContainer = document.querySelector(".panel-container"); const toggleButton = document.querySelector(".toggle-button"); requestAnimationFrame(function hide() { const { opacity } = getComputedStyle(panelContainer); if (opacity === "0") { panelContainer.classList.add("hidden"); if (toggleButton) { toggleButton.textContent = lang.showPanel; } return; } panelContainer.style.opacity = Number.parseFloat(opacity) - 0.1; requestAnimationFrame(hide); }); }; const showPanel = () => { const panelContainer = document.querySelector(".panel-container"); const toggleButton = document.querySelector(".toggle-button"); panelContainer.classList.remove("hidden"); requestAnimationFrame(function show() { const { opacity } = getComputedStyle(panelContainer); if (opacity === "1") { if (toggleButton) { toggleButton.textContent = lang.hidePanel; } return; } panelContainer.style.opacity = Number.parseFloat(opacity) + 0.1; requestAnimationFrame(show); }); }; const createUserInterface = () => { const toggleButton = document.createElement("button"); const toggleButtonContainer = document.createElement("div"); const expandButton = document.createElement("button"); const card = document.createElement("div"); const cardHeader = document.createElement("div"); const cardBody = document.createElement("div"); const cardFooter = document.createElement("div"); const statList = document.createElement("ul"); const controlContainer = document.createElement("div"); const importHageziTldsButton = document.createElement("button"); const removeHageziTldsButton = document.createElement("button"); const importHageziTldsAggressiveButton = document.createElement("button"); const removeHageziTldsAggressiveButton = document.createElement("button"); const importHageziTldAllowlistButton = document.createElement("button"); const removeHageziTldAllowlistButton = document.createElement("button"); const importErrorTrackersButton = document.createElement("button"); const removeErrorTrackersButton = document.createElement("button"); const importCustomListButton = document.createElement("button"); const removeCustomListButton = document.createElement("button"); const progress = document.createElement("p"); const loader = document.createElement("div"); loader.classList.add("text-center"); loader.innerHTML = `
`; statList.classList.add("stat-list"); expandButton.classList.add("btn", "panel-expand-button"); expandButton.type = "button"; expandButton.innerHTML = arrowDownHtml; expandButton.title = lang.expand; controlContainer.classList.add("panel-control-container", "hidden"); importHageziTldsButton.classList.add("btn", "btn-primary", "btn-sm"); removeHageziTldsButton.classList.add("btn", "btn-danger", "btn-sm"); importHageziTldsAggressiveButton.classList.add( "btn", "btn-primary", "btn-sm", ); removeHageziTldsAggressiveButton.classList.add( "btn", "btn-danger", "btn-sm", ); importHageziTldAllowlistButton.classList.add( "btn", "btn-primary", "btn-sm", ); removeHageziTldAllowlistButton.classList.add("btn", "btn-danger", "btn-sm"); importErrorTrackersButton.classList.add("btn", "btn-primary", "btn-sm"); removeErrorTrackersButton.classList.add("btn", "btn-danger", "btn-sm"); importCustomListButton.classList.add("btn", "btn-primary", "btn-sm"); removeCustomListButton.classList.add("btn", "btn-danger", "btn-sm"); importHageziTldsButton.type = "button"; removeHageziTldsButton.type = "button"; importHageziTldsAggressiveButton.type = "button"; removeHageziTldsAggressiveButton.type = "button"; importHageziTldAllowlistButton.type = "button"; removeHageziTldAllowlistButton.type = "button"; importErrorTrackersButton.type = "button"; removeErrorTrackersButton.type = "button"; importCustomListButton.type = "button"; removeCustomListButton.type = "button"; importHageziTldsButton.textContent = lang.importHageziTlds; removeHageziTldsButton.textContent = lang.removeHageziTlds; importHageziTldsAggressiveButton.textContent = lang.importHageziTldsAggressive; removeHageziTldsAggressiveButton.textContent = lang.removeHageziTldsAggressive; importHageziTldAllowlistButton.textContent = lang.importHageziTldAllowlist; removeHageziTldAllowlistButton.textContent = lang.removeHageziTldAllowlist; importErrorTrackersButton.textContent = lang.importErrorTrackers; removeErrorTrackersButton.textContent = lang.removeErrorTrackers; importCustomListButton.textContent = lang.importCustomList; removeCustomListButton.textContent = lang.removeCustomList; controlContainer.appendChild(importHageziTldsButton); controlContainer.appendChild(removeHageziTldsButton); controlContainer.appendChild(importHageziTldsAggressiveButton); controlContainer.appendChild(removeHageziTldsAggressiveButton); controlContainer.appendChild(importHageziTldAllowlistButton); controlContainer.appendChild(removeHageziTldAllowlistButton); controlContainer.appendChild(importErrorTrackersButton); controlContainer.appendChild(removeErrorTrackersButton); controlContainer.appendChild(importCustomListButton); controlContainer.appendChild(removeCustomListButton); progress.classList.add("panel-progress"); card.classList.add("card", "panel-container", "hidden"); card.style.opacity = "0"; cardHeader.classList.add("card-header"); cardHeader.innerHTML = `
HaGeZi to NextDNS
`; cardBody.classList.add("card-body"); cardBody.appendChild(loader); cardBody.appendChild(statList); cardBody.appendChild(progress); cardFooter.classList.add("card-footer", "panel-footer"); cardFooter.appendChild(expandButton); cardFooter.appendChild(controlContainer); toggleButtonContainer.classList.add( "nav-item", "d-flex", "align-items-center", "toggle-button-container", ); toggleButton.classList.add("btn", "btn-primary", "toggle-button"); toggleButton.textContent = toggleButton.classList.contains("hidden") ? lang.showPanel : lang.hidePanel; toggleButton.type = "button"; toggleButton.addEventListener("click", () => { const panelContainer = document.querySelector(".panel-container"); if (!panelContainer.classList.contains("hidden")) { hidePanel(); return; } showPanel(); }); card.appendChild(cardHeader); card.appendChild(cardBody); card.appendChild(cardFooter); toggleButtonContainer.appendChild(toggleButton); document.body.appendChild(card); return { card, cardBody, cardFooter, loader, progress, statList, expandButton, controlContainer, toggleButtonContainer, importHageziTldsButton, removeHageziTldsButton, importHageziTldsAggressiveButton, removeHageziTldsAggressiveButton, importHageziTldAllowlistButton, removeHageziTldAllowlistButton, importErrorTrackersButton, removeErrorTrackersButton, importCustomListButton, removeCustomListButton, }; }; const { cardBody, loader, progress, statList, expandButton, controlContainer, toggleButtonContainer, importHageziTldsButton, removeHageziTldsButton, importHageziTldsAggressiveButton, removeHageziTldsAggressiveButton, importHageziTldAllowlistButton, removeHageziTldAllowlistButton, importErrorTrackersButton, removeErrorTrackersButton, importCustomListButton, removeCustomListButton, } = createUserInterface(); const addToggleButton = () => { const observer = new MutationObserver((records, observer) => { for (const record of records) { const nav = document.querySelector(".nav"); if (nav && !nav.querySelector(".toggle-button")) { nav.appendChild(toggleButtonContainer); } if (record.target.childNodes.length === 0) { observer.disconnect(); } } }); observer.observe(document.body, { childList: true, subtree: true, }); }; const updateLoadingState = (loading) => { if (loading) { importHageziTldsButton.disabled = true; removeHageziTldsButton.disabled = true; importHageziTldsAggressiveButton.disabled = true; removeHageziTldsAggressiveButton.disabled = true; importHageziTldAllowlistButton.disabled = true; removeHageziTldAllowlistButton.disabled = true; importErrorTrackersButton.disabled = true; removeErrorTrackersButton.disabled = true; importCustomListButton.disabled = true; removeCustomListButton.disabled = true; return; } importHageziTldsButton.disabled = false; removeHageziTldsButton.disabled = false; importHageziTldsAggressiveButton.disabled = false; removeHageziTldsAggressiveButton.disabled = false; importHageziTldAllowlistButton.disabled = false; removeHageziTldAllowlistButton.disabled = false; importErrorTrackersButton.disabled = false; removeErrorTrackersButton.disabled = false; importCustomListButton.disabled = false; removeCustomListButton.disabled = false; }; const refreshStats = async () => { const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); updateLoadingState(true); const [tlds, denylist, allowlist] = await Promise.all([ nextApi .getSecurity() .then((res) => res.data.tlds.map(({ id }) => id.startsWith("xn--") ? punycode.toUnicode(id) : id, ), ), nextApi.getDenylist().then((res) => res.data.map(({ id }) => id)), nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)), ]); const tldContent = new Blob([tlds.toSorted().join("\n")], { type: "text/plain;charset=utf-8", }); const denylistContent = new Blob([denylist.toSorted().join("\n")], { type: "text/plain", }); const allowlistContent = new Blob([allowlist.toSorted().join("\n")], { type: "text/plain", }); updateLoadingState(false); statList.innerHTML = `
  • ${lang.numberOfBlockedTlds}: ${tlds.length}
  • ${lang.numberOfDeniedDomains}: ${denylist.length}
  • ${lang.numberOfAllowedDomains}: ${allowlist.length}
  • `; }; const attachButtonEvents = () => { expandButton.addEventListener("click", (event) => { const target = event.currentTarget; if (controlContainer.classList.contains("hidden")) { controlContainer.classList.remove("hidden"); target.title = lang.collapse; target.innerHTML = arrowUpHtml; return; } controlContainer.classList.add("hidden"); target.title = lang.expand; target.innerHTML = arrowDownHtml; }); importHageziTldsButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentTlds, hageziTlds] = await Promise.all([ nextApi .getSecurity() .then((res) => res.data.tlds.map(({ id }) => id)), Helper.getAbusedTlds(), ]); const tldsToImport = hageziTlds.difference(new Set(currentTlds)); let index = 1; for (const domain of tldsToImport) { progress.textContent = `${lang.adding} ${domain} (${index}/${tldsToImport.size})`; await nextApi.addTld(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); removeHageziTldsButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentTlds, hageziTlds] = await Promise.all([ nextApi .getSecurity() .then((res) => res.data.tlds.map(({ id }) => id)), Helper.getAbusedTlds(), ]); const tldsToRemove = hageziTlds.intersection(new Set(currentTlds)); let index = 1; for (const domain of tldsToRemove) { progress.textContent = `${lang.removing} ${domain} (${index}/${tldsToRemove.size})`; await nextApi.removeTld(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); importHageziTldsAggressiveButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentTlds, hageziTlds] = await Promise.all([ nextApi .getSecurity() .then((res) => res.data.tlds.map(({ id }) => id)), Helper.getAbusedTldsAggressive(), ]); const tldsToImport = hageziTlds.difference(new Set(currentTlds)); let index = 1; for (const domain of tldsToImport) { progress.textContent = `${lang.adding} ${domain} (${index}/${tldsToImport.size})`; await nextApi.addTld(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); removeHageziTldsAggressiveButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentTlds, hageziTlds] = await Promise.all([ nextApi .getSecurity() .then((res) => res.data.tlds.map(({ id }) => id)), Helper.getAbusedTldsAggressive(), ]); const tldsToRemove = hageziTlds.intersection(new Set(currentTlds)); let index = 1; for (const domain of tldsToRemove) { progress.textContent = `${lang.removing} ${domain} (${index}/${tldsToRemove.size})`; await nextApi.removeTld(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); importHageziTldAllowlistButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentAllowlist, hageziTldAllowlist] = await Promise.all([ nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)), Helper.getTldAllowlist(), ]); const domainsToImport = hageziTldAllowlist.difference( new Set(currentAllowlist), ); let index = 1; for (const domain of domainsToImport) { progress.textContent = `${lang.adding} ${domain} (${index}/${domainsToImport.size})`; await nextApi.allowlistDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); removeHageziTldAllowlistButton.addEventListener("click", async () => { if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentAllowlist, hageziTldAllowlist] = await Promise.all([ nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)), Helper.getTldAllowlist(), ]); const domainsToRemove = hageziTldAllowlist.intersection( new Set(currentAllowlist), ); let index = 1; for (const domain of domainsToRemove) { progress.textContent = `${lang.removing} ${domain} (${index}/${domainsToRemove.size})`; await nextApi.removeAllowedDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); importErrorTrackersButton.addEventListener("click", async () => { const listType = Helper.promptListType(); if (!listType) return; if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentList, errorTrackerList] = await Promise.all([ listType === ListType.Allowlist ? nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)) : nextApi.getDenylist().then((res) => res.data.map(({ id }) => id)), Helper.getErrorTrackerList(), ]); const domainsToImport = errorTrackerList.difference( new Set(currentList), ); const processDomain = listType === ListType.Allowlist ? nextApi.allowlistDomain.bind(nextApi) : nextApi.denylistDomain.bind(nextApi); let index = 1; for (const domain of domainsToImport) { progress.textContent = `${lang.adding} ${domain} (${index}/${domainsToImport.size})`; await processDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); removeErrorTrackersButton.addEventListener("click", async () => { const listType = Helper.promptListType(); if (!listType) return; if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentList, errorTrackerList] = await Promise.all([ listType === ListType.Allowlist ? nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)) : nextApi.getDenylist().then((res) => res.data.map(({ id }) => id)), Helper.getErrorTrackerList(), ]); const domainsToRemove = errorTrackerList.intersection( new Set(currentList), ); const processDomain = listType === ListType.Allowlist ? nextApi.removeAllowedDomain.bind(nextApi) : nextApi.removeDeniedDomain.bind(nextApi); let index = 1; for (const domain of domainsToRemove) { progress.textContent = `${lang.removing} ${domain} (${index}/${domainsToRemove.size})`; await processDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); importCustomListButton.addEventListener("click", async () => { const url = Helper.promptUrl(); if (!url) return; const listType = Helper.promptListType(); if (!listType) return; if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentList, importingList] = await Promise.all([ listType === ListType.Allowlist ? nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)) : nextApi.getDenylist().then((res) => res.data.map(({ id }) => id)), Helper.getWildcardList(url), ]); const domainsToImport = importingList.difference(new Set(currentList)); const processDomain = listType === ListType.Allowlist ? nextApi.allowlistDomain.bind(nextApi) : nextApi.denylistDomain.bind(nextApi); let index = 1; for (const domain of domainsToImport) { progress.textContent = `${lang.adding} ${domain} (${index}/${domainsToImport.size})`; await processDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); removeCustomListButton.addEventListener("click", async () => { const url = Helper.promptUrl(); if (!url) return; const listType = Helper.promptListType(); if (!listType) return; if (!Helper.confirmProceed()) return; updateLoadingState(true); const profileId = Helper.getProfileId(); const nextApi = new NextApi(profileId); try { const [currentList, removingList] = await Promise.all([ listType === ListType.Allowlist ? nextApi.getAllowlist().then((res) => res.data.map(({ id }) => id)) : nextApi.getDenylist().then((res) => res.data.map(({ id }) => id)), Helper.getWildcardList(url), ]); const domainsToImport = removingList.intersection(new Set(currentList)); const processDomain = listType === ListType.Allowlist ? nextApi.removeAllowedDomain.bind(nextApi) : nextApi.removeDeniedDomain.bind(nextApi); let index = 1; for (const domain of domainsToImport) { progress.textContent = `${lang.removing} ${domain} (${index}/${domainsToImport.size})`; await processDomain(domain); await Utility.sleep(); index++; } } catch (error) { progress.textContent = error.message; return; } await refreshStats(); progress.textContent = lang.done; updateLoadingState(false); }); }; addToggleButton(); attachButtonEvents(); let isInit = false; const init = async () => { if (!Helper.profileIdExists()) { hidePanel(); return; } showPanel(); addToggleButton(); if (isInit) return; statList.innerHTML = ""; cardBody.prepend(loader); await refreshStats(); cardBody.removeChild(loader); isInit = true; }; window.navigation?.addEventListener("navigatesuccess", init); init(); GM_addStyle(` .panel-container { position: fixed; right: 1rem; bottom: 1rem; } .toggle-button-container { justify-content: end; flex: 1; } .toggle-button { white-space: nowrap; } .stat-list { list-style: initial; } .panel-progress { margin: 0; } .panel-footer { display: flex; flex-direction: column; row-gap: .5rem; } .panel-control-container { display: flex; flex-direction: column; row-gap: .5rem; } .panel-control-container > .btn { width: fit-content; } .panel-footer > .panel-expand-button { display: flex; align-items: center; justify-content: center; width: auto; padding: 0; } .hidden { display: none; } .loader { width: 1.5rem; height: 1.5rem; border: 2px solid #000; border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: rotation 1s linear infinite; } @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `); })(window);