Updated to 1.1.0 and changes :P

- Fixed PaperMC hanger
- added PaperMC server downloader
- made it so that you set your remote path as the root of your minecraft server, and the script figures out where the files need to go
- Fixed Modrinth so that it also handles server distros like how Jenkins handler does it
- Added minecraft distro and version to confing.json.example, this is used right now, but i will be wrighting it into the script soon
- added LINCENSE file
This commit is contained in:
2025-07-23 12:42:26 -07:00
parent e4210aa9db
commit da7a8c5f3e
5 changed files with 135 additions and 40 deletions

144
index.js
View File

@@ -61,12 +61,18 @@ const ensureDir = (dir) => {
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
};
const DOWNLOAD_PATH = config.global.downloadPath || "downloads";
ensureDir(DOWNLOAD_PATH);
const PLUGIN_PATH = path.join(DOWNLOAD_PATH, "Plugins");
const SERVEREXEC_PATH = path.join(DOWNLOAD_PATH, "Serverexec");
[DOWNLOAD_PATH, PLUGIN_PATH, SERVEREXEC_PATH].forEach(ensureDir);
const isDownloaded = (filename) => fs.existsSync(path.join(DOWNLOAD_PATH, filename));
const downloadJar = async (url, name) => {
if (isDownloaded(name)) {
const isServerJar = name === "server.jar";
const destDir = isServerJar ? SERVEREXEC_PATH : PLUGIN_PATH;
const filePath = path.join(destDir, name);
if (fs.existsSync(filePath)) {
console.log(`🟡 Skipped (already exists): ${name}`);
return;
}
@@ -86,12 +92,12 @@ const downloadJar = async (url, name) => {
response.data.on("data", (chunk) => bar.increment(chunk.length));
response.data.on("end", () => bar.stop());
const filePath = path.join(DOWNLOAD_PATH, name);
await pipeline(response.data, fs.createWriteStream(filePath));
console.log(`✔️ Saved: ${name}`);
console.log(`✔️ Saved: ${filePath}`);
};
// --- Source handlers ---
// --- Handle Jenkins ---
const handleJenkins = async (url) => {
const html = await axios.get(url).then((res) => res.data);
const $ = cheerio.load(html);
@@ -131,6 +137,7 @@ const handleJenkins = async (url) => {
}
};
// --- Handle GitHub ---
const handleGitHub = async (url) => {
const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
if (!match) throw new Error("Invalid GitHub URL format");
@@ -147,6 +154,7 @@ const handleGitHub = async (url) => {
await downloadJar(chosen.browser_download_url, chosen.name);
};
// --- Handle Modrinth ---
const handleModrinth = async (url) => {
const match = url.match(/modrinth\.com\/plugin\/([^/]+)/);
if (!match) throw new Error("Invalid Modrinth URL format");
@@ -156,13 +164,22 @@ const handleModrinth = async (url) => {
.get(`https://api.modrinth.com/v2/project/${project}/version`)
.then((res) => res.data);
const latest = versions[0];
const file = latest.files.find((f) => f.filename.endsWith(".jar"));
if (!file) throw new Error("No .jar file in latest version");
// Filter to only versions compatible with Spigot/Paper/etc
const compatible = versions.find((v) =>
(v.loaders || []).some((loader) =>
["spigot", "paper", "bukkit", "purpur", "folia"].includes(loader.toLowerCase())
)
);
if (!compatible) throw new Error("No compatible Spigot/Paper version found");
const file = compatible.files.find((f) => f.filename.endsWith(".jar"));
if (!file) throw new Error("No .jar file in compatible version");
await downloadJar(file.url, file.filename);
};
// --- Handle Direct (Mainly for Floodgate) ---
const handleDirect = async (url) => {
let name = path.basename(url.split("?")[0]);
@@ -183,16 +200,21 @@ const handleDirect = async (url) => {
// --- Handle PaperMC ---
const handlePaperMC = async (url) => {
const versionMatch = url.match(/papermc.io\/download\/(.*?)(?:\/|$)/);
if (!versionMatch) throw new Error("Invalid PaperMC URL format");
const version = versionMatch[1];
if (url.includes("hangar.papermc.io")) {
const { data: html } = await axios.get(url);
const $ = cheerio.load(html);
const apiURL = `https://api.papermc.io/v2/projects/paper/versions/${version}/builds`;
const builds = await axios.get(apiURL).then((res) => res.data.builds);
const latestBuild = builds[0];
const jarLink = $('a[href$=".jar"]').first().attr("href");
if (!jarLink) throw new Error("❌ No .jar link found on Hangar page");
const downloadURL = latestBuild.downloads.application.url;
await downloadJar(downloadURL, `paper-${version}-${latestBuild.build}.jar`);
const downloadURL = jarLink.startsWith("http")
? jarLink
: `https://hangar.papermc.io${jarLink}`;
const fileName = path.basename(downloadURL);
await downloadJar(downloadURL, fileName);
return;
}
};
// --- Handle dev.bukkit.org ---
@@ -236,6 +258,26 @@ const handleBukkit = async (url) => {
await downloadJar(fullDownloadLink, filename);
};
// --- Handle PaperMC Server DL ---
const downloadLatestPaperMC = async () => {
console.log("📥 Downloading latest PaperMC server jar...");
const projectURL = 'https://api.papermc.io/v2/projects/paper';
const { data: versionsData } = await axios.get(projectURL);
const latestVersion = versionsData.versions[versionsData.versions.length - 1];
const buildsURL = `${projectURL}/versions/${latestVersion}`;
const { data: buildsData } = await axios.get(buildsURL);
const latestBuild = buildsData.builds[buildsData.builds.length - 1];
const jarInfoURL = `${projectURL}/versions/${latestVersion}/builds/${latestBuild}`;
const { data: jarInfo } = await axios.get(jarInfoURL);
const jarPath = jarInfo.downloads.application.name;
const downloadURL = `https://api.papermc.io/v2/projects/paper/versions/${latestVersion}/builds/${latestBuild}/downloads/${jarPath}`;
await downloadJar(downloadURL, "server.jar");
};
// --- Upload to SFTP ---
const uploadToSFTP = async () => {
@@ -246,8 +288,6 @@ const uploadToSFTP = async () => {
const sftp = new SftpClient();
const remote = sftpConfig.remotePath || "/";
const localFiles = fs.readdirSync(DOWNLOAD_PATH).filter(f => f.endsWith(".jar"));
const connectOptions = {
host: sftpConfig.host,
port: sftpConfig.port || 22,
@@ -268,29 +308,56 @@ const uploadToSFTP = async () => {
try {
await sftp.connect(connectOptions);
const remoteFiles = await sftp.list(remote);
const remoteJars = remoteFiles.filter(f => f.name.endsWith(".jar"));
for (const localFile of localFiles) {
const baseName = extractBaseName(localFile);
const toDelete = remoteJars.filter(remoteFile =>
extractBaseName(remoteFile.name) === baseName
);
for (const file of toDelete) {
const fullPath = path.posix.join(remote, file.name);
await sftp.delete(fullPath);
console.log(`🗑️ Deleted remote: ${file.name}`);
const uploadFolder = async (localDir, remoteDir) => {
const files = fs.readdirSync(localDir).filter(f => f.endsWith(".jar"));
const remoteFiles = await sftp.list(remoteDir);
const remoteJars = remoteFiles.filter(f => f.name.endsWith(".jar"));
for (const file of files) {
const baseName = extractBaseName(file);
const toDelete = remoteJars.filter(r => extractBaseName(r.name) === baseName);
for (const del of toDelete) {
const fullPath = path.posix.join(remoteDir, del.name);
await sftp.delete(fullPath);
console.log(`🗑️ Deleted remote: ${fullPath}`);
}
const localPath = path.join(localDir, file);
const remotePath = path.posix.join(remoteDir, file);
await sftp.fastPut(localPath, remotePath);
console.log(`⬆️ Uploaded to ${remoteDir}: ${file}`);
}
// 🚀 Upload new files
const localPath = path.join(DOWNLOAD_PATH, localFile);
const remotePath = path.posix.join(remote, localFile);
await sftp.fastPut(localPath, remotePath);
console.log(`⬆️ Uploaded: ${localFile}`);
}
};
await uploadFolder(PLUGIN_PATH, path.posix.join(remote, "plugins"));
await uploadFolder(SERVEREXEC_PATH, remote);
} catch (err) {
console.error("❌ SFTP Error:", err.message);
} finally {
sftp.end();
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout closing SFTP connection")), 5000)
);
try {
await Promise.race([sftp.end(), timeout]);
console.log("🔌 SFTP connection closed.");
// Clean up local files after upload
const deleteFilesInDir = (dir) => {
fs.readdirSync(dir).forEach(file => {
const filePath = path.join(dir, file);
if (fs.lstatSync(filePath).isFile()) {
fs.unlinkSync(filePath);
console.log(`🧹 Deleted: ${filePath}`);
}
});
};
deleteFilesInDir(PLUGIN_PATH);
deleteFilesInDir(SERVEREXEC_PATH);
} catch (e) {
console.warn("⚠️ Error or timeout closing SFTP:", e.message);
}
}
};
@@ -304,6 +371,9 @@ const uploadToSFTP = async () => {
console.log(`🗑️ Deleted local file: ${file}`);
}
await downloadLatestPaperMC();
console.log("\n🔍 Starting plugin downloads from configured URLs...");
for (const url of config.urls) {
console.log(`\n📥 ${url}`);
try {