Files
SupportBot/Structures/AddonCatalog.js
T
2026-07-26 20:33:00 +01:00

186 lines
4.9 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const axios = require("axios");
const { withTimeout } = require("./asyncTimeout.js");
const AdmZip = require("adm-zip");
const yaml = require("js-yaml");
const EMERALD_API = "https://emeraldsrv.dev/api/v1";
function getApiKey() {
try {
if (fs.existsSync("./Configs/api.yml")) {
const config = yaml.load(fs.readFileSync("./Configs/api.yml", "utf8"));
return config?.API?.EmeraldAPIKey || "";
}
} catch (e) {
console.error("Failed to read api.yml:", e);
}
return "";
}
function ensureAddonDirs() {
if (!fs.existsSync("./Addons")) {
fs.mkdirSync("./Addons", { recursive: true });
}
if (!fs.existsSync("./Addons/Configs")) {
fs.mkdirSync("./Addons/Configs", { recursive: true });
}
}
async function listCatalogAddons() {
const key = getApiKey();
if (!key) throw new Error("EmeraldAPIKey not configured in api.yml");
const res = await withTimeout(
axios.get(`${EMERALD_API}/resources`, {
headers: { Authorization: `Bearer ${key}` },
timeout: 15_000,
}),
18_000,
"Emerald API request timed out",
);
if (res.data.status !== "success") {
throw new Error(res.data.message || "Failed to fetch catalog");
}
return res.data.data.map(item => ({
id: item.id.toString(),
name: item.title,
slug: item.slug,
description: `Emerald Services Resource - ${item.slug}`,
price: item.price,
is_external: item.is_external,
external_store: item.external_store,
repositoryUrl: item.external_url || `https://emeraldsrv.dev`,
jsFiles: [],
configFiles: []
})).sort((a, b) => a.name.localeCompare(b.name));
}
async function downloadFile(id) {
const key = getApiKey();
const res = await withTimeout(
axios.get(`${EMERALD_API}/download?id=${id}`, {
headers: { Authorization: `Bearer ${key}` },
responseType: "arraybuffer",
timeout: 30_000
}),
35_000,
"Addon file download timed out",
);
return Buffer.from(res.data);
}
function listLocalAddonJs() {
ensureAddonDirs();
return fs
.readdirSync("./Addons")
.filter((f) => f.endsWith(".js") && f !== "addon.js");
}
function listLocalAddonConfigs() {
ensureAddonDirs();
const dir = "./Addons/Configs";
if (!fs.existsSync(dir)) return [];
return fs
.readdirSync(dir)
.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml") || f.endsWith(".json"));
}
function isAddonInstalled(catalogEntry) {
const localJs = listLocalAddonJs();
return localJs.some(f => f.includes(catalogEntry.slug) || f.includes(catalogEntry.id.toString()) || f.includes(catalogEntry.name.replace(/ /g, '')));
}
async function listInstalledAddons(catalog) {
const localJs = listLocalAddonJs();
const installed = [];
for (const file of localJs) {
const name = file.replace(/\.js$/, "");
const match = catalog.find(c => c.slug === name || c.id.toString() === name || c.name.replace(/ /g, '') === name);
installed.push({
id: match ? match.id : name,
name: match ? match.name : name,
jsFiles: [file],
configFiles: listLocalAddonConfigs().filter(f => f.startsWith(name)),
custom: !match
});
}
return installed;
}
async function installAddonFromCatalog(addonId) {
const catalog = await listCatalogAddons();
const addon = catalog.find(c => c.id === addonId.toString());
if (!addon) {
throw new Error(`Addon not found in catalog`);
}
ensureAddonDirs();
const content = await downloadFile(addon.id);
const zip = new AdmZip(content);
zip.extractAllTo("./Addons", true);
return {
addon: addon,
filesWritten: [],
};
}
function readAddonConfig(filename) {
const safe = path.basename(filename);
if (!/^[a-zA-Z0-9_.-]+\.(yml|yaml|json)$/.test(safe)) {
throw new Error("Invalid config filename");
}
const filePath = path.join("./Addons/Configs", safe);
if (!fs.existsSync(filePath)) {
throw new Error("Addon config not found");
}
const raw = fs.readFileSync(filePath, "utf8");
if (safe.endsWith(".json")) {
return { filename: safe, format: "json", data: JSON.parse(raw), raw };
}
const yaml = require("js-yaml");
return { filename: safe, format: "yaml", data: yaml.load(raw) || {}, raw };
}
function writeAddonConfig(filename, data) {
const safe = path.basename(filename);
if (!/^[a-zA-Z0-9_.-]+\.(yml|yaml|json)$/.test(safe)) {
throw new Error("Invalid config filename");
}
ensureAddonDirs();
const filePath = path.join("./Addons/Configs", safe);
let content;
if (safe.endsWith(".json")) {
content = `${JSON.stringify(data, null, 2)}\n`;
} else {
const yaml = require("js-yaml");
content = yaml.dump(data, { lineWidth: 120, noRefs: true });
}
fs.writeFileSync(filePath, content);
return { filename: safe, path: filePath.replace(/\\/g, "/") };
}
module.exports = {
listCatalogAddons,
listInstalledAddons,
isAddonInstalled,
installAddonFromCatalog,
listLocalAddonConfigs,
readAddonConfig,
writeAddonConfig,
};