// ==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(/^(\|\|)?(?<domain>(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(/^(\*\.)*(?<domain>[\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 = `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>`;
const arrowUpHtml = `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-528 296-344l-56-56 240-240 240 240-56 56-184-184Z"/></svg>`;
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 = `<div class="loader" />`;
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 = `<h6 class="mb-0 text-center">HaGeZi to NextDNS</h6>`;
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 = `
<li>${lang.numberOfBlockedTlds}: <a href="${URL.createObjectURL(tldContent)}" target="_blank">${tlds.length}</a></li>
<li>${lang.numberOfDeniedDomains}: <a href="${URL.createObjectURL(denylistContent)}" target="_blank">${denylist.length}</a></li>
<li>${lang.numberOfAllowedDomains}: <a href="${URL.createObjectURL(allowlistContent)}" target="_blank">${allowlist.length}</a></li>
`;
};
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);