mirror of
https://github.com/H4zeyaf/Breakallery.git
synced 2026-10-17 16:01:35 +02:00
205 lines
4.9 KiB
JavaScript
205 lines
4.9 KiB
JavaScript
const net = require('net');
|
|
const ftp = require('basic-ftp');
|
|
const { Writable } = require('stream');
|
|
|
|
class FTPManager {
|
|
constructor(config) {
|
|
this.config = config.ps5;
|
|
this._isOnline = false;
|
|
this._lastChecked = 0;
|
|
this._checkingPromise = null;
|
|
this._heartbeatTimer = null;
|
|
|
|
this.startHeartbeat();
|
|
}
|
|
|
|
startHeartbeat(intervalMs = 8000) {
|
|
this.stopHeartbeat();
|
|
this.checkOnline(1500).catch(() => {});
|
|
this._heartbeatTimer = setInterval(() => {
|
|
this.checkOnline(1500).catch(() => {});
|
|
}, intervalMs);
|
|
if (this._heartbeatTimer && this._heartbeatTimer.unref) {
|
|
this._heartbeatTimer.unref();
|
|
}
|
|
}
|
|
|
|
stopHeartbeat() {
|
|
if (this._heartbeatTimer) {
|
|
clearInterval(this._heartbeatTimer);
|
|
this._heartbeatTimer = null;
|
|
}
|
|
}
|
|
|
|
async checkOnline(timeout = 1500) {
|
|
if (this._checkingPromise) {
|
|
return this._checkingPromise;
|
|
}
|
|
|
|
this._checkingPromise = new Promise((resolve) => {
|
|
const socket = new net.Socket();
|
|
let settled = false;
|
|
|
|
const finish = (online) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
this._isOnline = online;
|
|
this._lastChecked = Date.now();
|
|
socket.destroy();
|
|
resolve(online);
|
|
};
|
|
|
|
socket.setTimeout(timeout);
|
|
socket.once('connect', () => finish(true));
|
|
socket.once('timeout', () => finish(false));
|
|
socket.once('error', () => finish(false));
|
|
|
|
try {
|
|
socket.connect(parseInt(this.config.port, 10) || 1337, this.config.host);
|
|
} catch {
|
|
finish(false);
|
|
}
|
|
}).finally(() => {
|
|
this._checkingPromise = null;
|
|
});
|
|
|
|
return this._checkingPromise;
|
|
}
|
|
|
|
isOnline() {
|
|
return !!this._isOnline;
|
|
}
|
|
|
|
updateConfig(ps5Config) {
|
|
this.config = { ...this.config, ...ps5Config };
|
|
this._isOnline = false;
|
|
this.checkOnline(1500).catch(() => {});
|
|
}
|
|
|
|
async getClient() {
|
|
const client = new ftp.Client();
|
|
client.ftp.verbose = false;
|
|
client.ftp.timeout = this.config.timeout || 6000;
|
|
try {
|
|
await client.access({
|
|
host: this.config.host,
|
|
port: parseInt(this.config.port, 10) || 1337,
|
|
user: this.config.user || 'anonymous',
|
|
password: this.config.password || '',
|
|
secure: false,
|
|
});
|
|
return client;
|
|
} catch (err) {
|
|
client.close();
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async testConnection(host, port) {
|
|
const client = new ftp.Client();
|
|
client.ftp.verbose = false;
|
|
client.ftp.timeout = 4000;
|
|
try {
|
|
await client.access({
|
|
host: host || this.config.host,
|
|
port: parseInt(port || this.config.port, 10) || 1337,
|
|
user: this.config.user || 'anonymous',
|
|
password: this.config.password || '',
|
|
secure: false,
|
|
});
|
|
|
|
// Try checking if /user or / exists using cd for ftpsrv compatibility
|
|
let rootList = [];
|
|
try {
|
|
await client.cd('/user');
|
|
rootList = await client.list();
|
|
} catch {
|
|
await client.cd('/');
|
|
rootList = await client.list();
|
|
}
|
|
|
|
client.close();
|
|
if (!host || host === this.config.host) {
|
|
this._isOnline = true;
|
|
this._lastChecked = Date.now();
|
|
}
|
|
return {
|
|
success: true,
|
|
message: 'Successfully connected to PS5 FTP server!',
|
|
itemsFound: rootList.length,
|
|
};
|
|
} catch (err) {
|
|
client.close();
|
|
if (!host || host === this.config.host) {
|
|
this._isOnline = false;
|
|
this._lastChecked = Date.now();
|
|
}
|
|
return {
|
|
success: false,
|
|
message: `Connection failed: ${err.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
async list(remotePath) {
|
|
const client = await this.getClient();
|
|
try {
|
|
if (remotePath) {
|
|
await client.cd(remotePath);
|
|
}
|
|
return await client.list();
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
async size(remotePath) {
|
|
const client = await this.getClient();
|
|
try {
|
|
return await client.size(remotePath);
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
async getBuffer(remotePath) {
|
|
const client = await this.getClient();
|
|
const chunks = [];
|
|
const memoryStream = new Writable({
|
|
write(chunk, encoding, callback) {
|
|
chunks.push(chunk);
|
|
callback();
|
|
},
|
|
});
|
|
|
|
try {
|
|
await client.downloadTo(memoryStream, remotePath);
|
|
return Buffer.concat(chunks);
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
async streamTo(remotePath, outputStream, startAt = 0) {
|
|
const client = await this.getClient();
|
|
try {
|
|
// client.downloadTo supports startAt offset (FTP REST command)
|
|
await client.downloadTo(outputStream, remotePath, startAt);
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
async downloadFile(remotePath, localPath) {
|
|
const client = await this.getClient();
|
|
try {
|
|
await client.downloadTo(localPath, remotePath);
|
|
return true;
|
|
} finally {
|
|
client.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = FTPManager;
|