153 lines
4.1 KiB
JavaScript
153 lines
4.1 KiB
JavaScript
require("dotenv").config();
|
|||
|
|
const fs = require("fs");
|
||
|
|
const path = require("path");
|
||
|
|
const yaml = require("js-yaml");
|
||
|
|
const readline = require("node:readline/promises");
|
||
|
|
const { stdin: input, stdout: output } = require("node:process");
|
||
|
|
|
||
|
|
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||
|
|
const DISCORD_API = "https://discord.com/api/v10";
|
||
|
|
const ROLES_PATH = path.join(__dirname, "roles.yaml");
|
||
|
|
|
||
|
|
if (!DISCORD_BOT_TOKEN) {
|
||
|
|
console.error("DISCORD_BOT_TOKEN is not set in .env. Run bash install.sh first to set it up.");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadRoles() {
|
||
|
|
try {
|
||
|
|
const data = yaml.load(fs.readFileSync(ROLES_PATH, "utf8"));
|
||
|
|
return (data && typeof data === "object") ? data : {};
|
||
|
|
} catch (err) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function sleep(ms) {
|
||
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
|
|
}
|
||
|
|
|
||
|
|
async function fetchBotGuilds() {
|
||
|
|
const res = await fetch(DISCORD_API + "/users/@me/guilds", {
|
||
|
|
headers: { Authorization: "Bot " + DISCORD_BOT_TOKEN }
|
||
|
|
});
|
||
|
|
if (!res.ok) throw new Error("Discord guild list failed: " + res.status);
|
||
|
|
return res.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function fetchGuildRoles(guildId) {
|
||
|
|
const res = await fetch(DISCORD_API + "/guilds/" + guildId + "/roles", {
|
||
|
|
headers: { Authorization: "Bot " + DISCORD_BOT_TOKEN }
|
||
|
|
});
|
||
|
|
if (!res.ok) throw new Error("Discord role list failed: " + res.status);
|
||
|
|
return res.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function renameRole(guildId, roleId, name) {
|
||
|
|
const res = await fetch(DISCORD_API + "/guilds/" + guildId + "/roles/" + roleId, {
|
||
|
|
method: "PATCH",
|
||
|
|
headers: {
|
||
|
|
Authorization: "Bot " + DISCORD_BOT_TOKEN,
|
||
|
|
"Content-Type": "application/json"
|
||
|
|
},
|
||
|
|
body: JSON.stringify({ name: name })
|
||
|
|
});
|
||
|
|
if (!res.ok) throw new Error("role rename failed: " + res.status + " " + (await res.text()));
|
||
|
|
}
|
||
|
|
|
||
|
|
async function pickFromList(rl, label, items, formatItem) {
|
||
|
|
console.log("");
|
||
|
|
console.log(label);
|
||
|
|
items.forEach((item, i) => {
|
||
|
|
console.log(" " + (i + 1) + ". " + formatItem(item));
|
||
|
|
});
|
||
|
|
|
||
|
|
while (true) {
|
||
|
|
const answer = (await rl.question("Enter a number (or 0 to cancel): ")).trim();
|
||
|
|
const n = parseInt(answer, 10);
|
||
|
|
if (n === 0) return null;
|
||
|
|
if (!isNaN(n) && n >= 1 && n <= items.length) return items[n - 1];
|
||
|
|
console.log("Not a valid choice, try again.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const roles = loadRoles();
|
||
|
|
const keys = Object.keys(roles);
|
||
|
|
if (keys.length === 0) {
|
||
|
|
console.log("No roles in roles.yaml, nothing to sync.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let guilds;
|
||
|
|
try {
|
||
|
|
guilds = await fetchBotGuilds();
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Could not load your Discord server list:", err.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!guilds || guilds.length === 0) {
|
||
|
|
console.error("The bot is not in any Discord server yet.");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
let guild = guilds[0];
|
||
|
|
if (guilds.length > 1) {
|
||
|
|
const rl = readline.createInterface({ input, output });
|
||
|
|
guild = await pickFromList(rl, "Which Discord server?", guilds, (g) => g.name);
|
||
|
|
rl.close();
|
||
|
|
if (!guild) return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let discordRoles;
|
||
|
|
try {
|
||
|
|
discordRoles = await fetchGuildRoles(guild.id);
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Could not load server roles:", err.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const byId = {};
|
||
|
|
discordRoles.forEach((r) => {
|
||
|
|
byId[r.id] = r;
|
||
|
|
});
|
||
|
|
|
||
|
|
let updated = 0;
|
||
|
|
let skipped = 0;
|
||
|
|
let failed = 0;
|
||
|
|
|
||
|
|
for (const key of keys) {
|
||
|
|
const entry = roles[key];
|
||
|
|
const desiredName = entry.emoji + " " + entry.title;
|
||
|
|
const current = byId[entry.roleId];
|
||
|
|
|
||
|
|
if (!current) {
|
||
|
|
console.error("Role for \"" + entry.title + "\" no longer exists in Discord, skipping.");
|
||
|
|
skipped++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (current.name === desiredName) {
|
||
|
|
skipped++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await renameRole(guild.id, entry.roleId, desiredName);
|
||
|
|
console.log("Updated \"" + current.name + "\" to \"" + desiredName + "\".");
|
||
|
|
updated++;
|
||
|
|
} catch (err) {
|
||
|
|
failed++;
|
||
|
|
console.error("Could not update the role for \"" + entry.title + "\":", err.message);
|
||
|
|
}
|
||
|
|
await sleep(300);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log("");
|
||
|
|
console.log("Done. Updated: " + updated + ", already in sync or skipped: " + skipped +
|
||
|
|
(failed > 0 ? ", failed: " + failed : "") + ".");
|
||
|
|
}
|
||
|
|
|
||
|
|
main();
|