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, };