// ==UserScript==
// @name Cloudflare force create a non-glue record that is beneath a delegated child zone.
// @namespace https://medium.com/chouhsiang
// @version 1.2
// @description Plugin for Cloudflare. Fix error for: Cannot create a non-glue record that is beneath a delegated child zone. (Code: 89018).
// @author Sean Chou
// @match https://dash.cloudflare.com/*
// @icon
// @grant none
// @license MIT
// ==/UserScript==
async function submit(ele) {
const u = new URL(window.location);
const path = u.pathname.split("/");
const account = path[1];
const domain = path[2];
// Check Insert or Update
const form = ele.form;
let record_id = form.parentElement.parentElement.id;
record_id = record_id == "" ? null : record_id.split("-")[0];
// Get zone id
let url = `https://dash.cloudflare.com/api/v4/zones?name=${domain}&account.id=${account}`;
let response = await fetch(url);
let data = await response.json();
const zone = data.result[0].id;
// Get atok
url = "https://dash.cloudflare.com/api/v4/system/bootstrap";
response = await fetch(url);
data = await response.json();
const atok = data.result.data.atok;
// Get form data
const type = form.querySelector("[aria-labelledby=dns-label-type]").value;
let name = form.querySelector("[aria-labelledby=dns-label-name]").value;
if (!name.endsWith(domain)) {
name += "." + domain;
}
let content;
if (type == "A") {
content = form.querySelector(
"[aria-labelledby=dns-label-ipv4_address]"
).value;
} else if (type == "AAAA") {
content = form.querySelector(
"[aria-labelledby=dns-label-ipv6_address]"
).value;
} else if (type == "CNAME") {
content = form.querySelector("[aria-labelledby=dns-label-target]").value;
} else {
alert("Only support A、AAAA、CNAME");
return;
}
// Get ns record & delete
const parent_domain = name.split(".").slice(1).join(".");
url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records?type=NS&name=${parent_domain}`;
response = await fetch(url);
data = await response.json();
const ns = data.result;
for (const i of ns) {
const dns_id = i.id;
url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records/${dns_id}`;
await fetch(url, {
method: "DELETE",
headers: {
"X-Atok": atok,
"X-Cross-Site-Security": "dash",
},
});
}
// Insert or update record
let d = {
content: content,
name: name,
type: type,
zone_id: zone,
zone_name: domain,
proxied: true,
ttl: 1,
comment: "",
data: {},
proxiable: true,
tags: [],
};
if (record_id) {
url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records/${record_id}`;
response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"X-Atok": atok,
"X-Cross-Site-Security": "dash",
},
body: JSON.stringify(d),
});
data = await response.json();
if (data.success) {
alert("Force update succeeded");
} else {
alert("Force update failed:" + JSON.stringify(data.errors));
}
} else {
url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records`;
response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Atok": atok,
"X-Cross-Site-Security": "dash",
},
body: JSON.stringify(d),
});
data = await response.json();
if (data.success) {
alert("Force create succeeded");
} else {
alert("Force create succeeded:" + JSON.stringify(data.errors));
}
}
// Add ns record
for (const i of ns) {
url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records`;
d = {
content: i.content,
name: i.name,
type: "NS",
zone_id: zone,
zone_name: domain,
};
await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Atok": atok,
"X-Cross-Site-Security": "dash",
},
body: JSON.stringify(d),
});
}
location.reload();
}
async function addBtn() {
const u = new URL(window.location);
if (u.host == "dash.cloudflare.com" && u.pathname.endsWith("/dns/records")) {
const old_btns = document.querySelectorAll(
"button[data-testid=dns-record-form-save-button]"
);
const new_btns = document.querySelectorAll(
"button[data-testid=force-save-button]"
);
if (old_btns.length > new_btns.length) {
const new_btn = document.createElement("button");
new_btn.setAttribute("data-testid", "force-save-button");
new_btn.setAttribute("type", "button");
new_btn.setAttribute("class", old_btns[0].className);
new_btn.setAttribute(
"style",
"margin-left: 8px; background-color: rgb(235, 20, 3)"
);
new_btn.innerText = "Force Save";
new_btn.onclick = async function () {
await submit(this);
};
old_btns.forEach(function (old_btn) {
if (old_btn.nextSibling == null) {
old_btn.parentNode.insertBefore(new_btn, old_btn.nextSibling);
}
});
}
}
}
setInterval(async () => await addBtn(), 1000);