136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
const axios = require('axios');
|
|
|
|
const ACCEPT_HEADER = 'Application/vnd.pterodactyl.v1+json';
|
|
|
|
const defaultBaseUrl = () => process.env.PTERODACTYL_API_URL || 'https://panel.louismazin.ovh';
|
|
const defaultServerId = () => process.env.PTERODACTYL_SERVER_ID || 'ae4a628f';
|
|
|
|
const withAcceptHeader = (headers) => {
|
|
// Keep existing headers behavior, but ensure we can talk to Pterodactyl v1.
|
|
return {
|
|
...(headers || {}),
|
|
Accept: headers?.Accept || ACCEPT_HEADER,
|
|
};
|
|
};
|
|
|
|
const encodeQuery = (v) => encodeURIComponent(v);
|
|
|
|
async function listDirectory({ headers, baseUrl, serverId, directory }) {
|
|
const url = `${baseUrl}/api/client/servers/${serverId}/files/list?directory=${encodeQuery(directory || '/')}`;
|
|
const res = await axios.get(url, { headers: withAcceptHeader(headers) });
|
|
const data = res?.data?.data || res?.data?.attributes?.data || res?.data?.data;
|
|
|
|
// Standard response: { object:'list', data:[{object:'file_object', attributes:{...}}] }
|
|
if (res?.data?.object === 'list' && Array.isArray(res?.data?.data)) {
|
|
return res.data.data.map((x) => x.attributes);
|
|
}
|
|
|
|
// Fallback: try best-effort.
|
|
if (Array.isArray(data)) return data;
|
|
return [];
|
|
}
|
|
|
|
async function getDownloadUrl({ headers, baseUrl, serverId, file }) {
|
|
const url = `${baseUrl}/api/client/servers/${serverId}/files/download?file=${encodeQuery(file)}`;
|
|
|
|
// Most panels return JSON: { object:'signed_url', attributes:{ url } }
|
|
const res = await axios.get(url, { headers: withAcceptHeader(headers), validateStatus: () => true });
|
|
|
|
if (res.status >= 400) {
|
|
const msg = typeof res.data === 'string' ? res.data : JSON.stringify(res.data);
|
|
throw new Error(`Pterodactyl download URL failed (${res.status}): ${msg}`);
|
|
}
|
|
|
|
if (res.data && typeof res.data === 'object') {
|
|
const signed =
|
|
res.data?.attributes?.url ||
|
|
res.data?.data?.attributes?.url ||
|
|
res.data?.url;
|
|
if (signed) return String(signed);
|
|
}
|
|
|
|
// Some installations may directly return the file, but in that case we don't have a URL.
|
|
return null;
|
|
}
|
|
|
|
async function downloadFile({ headers, baseUrl, serverId, file }) {
|
|
const signedUrl = await getDownloadUrl({ headers, baseUrl, serverId, file });
|
|
|
|
if (signedUrl) {
|
|
const res = await axios.get(signedUrl, {
|
|
responseType: 'arraybuffer',
|
|
maxRedirects: 5,
|
|
validateStatus: () => true,
|
|
});
|
|
if (res.status >= 400) {
|
|
throw new Error(`Signed download failed (${res.status}) for ${file}`);
|
|
}
|
|
return Buffer.from(res.data);
|
|
}
|
|
|
|
// Fallback attempt: direct download from API.
|
|
const direct = `${baseUrl}/api/client/servers/${serverId}/files/download?file=${encodeQuery(file)}`;
|
|
const res = await axios.get(direct, {
|
|
headers: withAcceptHeader(headers),
|
|
responseType: 'arraybuffer',
|
|
maxRedirects: 5,
|
|
validateStatus: () => true,
|
|
});
|
|
if (res.status >= 400) {
|
|
throw new Error(`Direct download failed (${res.status}) for ${file}`);
|
|
}
|
|
return Buffer.from(res.data);
|
|
}
|
|
|
|
async function getUploadUrl({ headers, baseUrl, serverId, directory }) {
|
|
const url = `${baseUrl}/api/client/servers/${serverId}/files/upload?directory=${encodeQuery(directory || '/')}`;
|
|
const res = await axios.get(url, { headers: withAcceptHeader(headers) });
|
|
const signed = res?.data?.attributes?.url || res?.data?.data?.attributes?.url;
|
|
if (!signed) {
|
|
throw new Error('Could not get signed upload URL from Pterodactyl');
|
|
}
|
|
return String(signed);
|
|
}
|
|
|
|
async function uploadSingleFile({ headers, baseUrl, serverId, directory, filename, content }) {
|
|
const signed = await getUploadUrl({ headers, baseUrl, serverId, directory });
|
|
|
|
// Use fetch + FormData (Node 18+) to match Pterodactyl upload behavior.
|
|
const form = new FormData();
|
|
const blob = new Blob([content]);
|
|
form.append('files', blob, filename);
|
|
|
|
const uploadUrl = `${signed}${signed.includes('?') ? '&' : '?'}directory=${encodeQuery(directory || '/')}`;
|
|
|
|
const res = await fetch(uploadUrl, {
|
|
method: 'POST',
|
|
body: form,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`Upload failed (${res.status}): ${text}`);
|
|
}
|
|
}
|
|
|
|
async function deleteFiles({ headers, baseUrl, serverId, root, files }) {
|
|
const url = `${baseUrl}/api/client/servers/${serverId}/files/delete`;
|
|
await axios.post(
|
|
url,
|
|
{
|
|
root,
|
|
files,
|
|
},
|
|
{ headers: withAcceptHeader(headers) }
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
defaultBaseUrl,
|
|
defaultServerId,
|
|
listDirectory,
|
|
downloadFile,
|
|
uploadSingleFile,
|
|
deleteFiles,
|
|
};
|