@dimzxzzx07/mc-headless 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +0 -0
  2. package/dist/core/ConfigHandler.d.ts +12 -0
  3. package/dist/core/ConfigHandler.d.ts.map +1 -0
  4. package/dist/core/ConfigHandler.js +118 -0
  5. package/dist/core/ConfigHandler.js.map +1 -0
  6. package/dist/core/JavaChecker.d.ts +8 -0
  7. package/dist/core/JavaChecker.d.ts.map +1 -0
  8. package/dist/core/JavaChecker.js +68 -0
  9. package/dist/core/JavaChecker.js.map +1 -0
  10. package/dist/core/MinecraftServer.d.ts +28 -0
  11. package/dist/core/MinecraftServer.d.ts.map +1 -0
  12. package/dist/core/MinecraftServer.js +312 -0
  13. package/dist/core/MinecraftServer.js.map +1 -0
  14. package/dist/core/ServerManager.d.ts +30 -0
  15. package/dist/core/ServerManager.d.ts.map +1 -0
  16. package/dist/core/ServerManager.js +203 -0
  17. package/dist/core/ServerManager.js.map +1 -0
  18. package/dist/engines/Downloader.d.ts +16 -0
  19. package/dist/engines/Downloader.d.ts.map +1 -0
  20. package/dist/engines/Downloader.js +159 -0
  21. package/dist/engines/Downloader.js.map +1 -0
  22. package/dist/engines/FabricEngine.d.ts +11 -0
  23. package/dist/engines/FabricEngine.d.ts.map +1 -0
  24. package/dist/engines/FabricEngine.js +70 -0
  25. package/dist/engines/FabricEngine.js.map +1 -0
  26. package/dist/engines/ForgeEngine.d.ts +11 -0
  27. package/dist/engines/ForgeEngine.d.ts.map +1 -0
  28. package/dist/engines/ForgeEngine.js +84 -0
  29. package/dist/engines/ForgeEngine.js.map +1 -0
  30. package/dist/engines/PaperEngine.d.ts +11 -0
  31. package/dist/engines/PaperEngine.d.ts.map +1 -0
  32. package/dist/engines/PaperEngine.js +88 -0
  33. package/dist/engines/PaperEngine.js.map +1 -0
  34. package/dist/engines/ServerEngine.d.ts +10 -0
  35. package/dist/engines/ServerEngine.d.ts.map +1 -0
  36. package/dist/engines/ServerEngine.js +3 -0
  37. package/dist/engines/ServerEngine.js.map +1 -0
  38. package/dist/engines/VanillaEngine.d.ts +11 -0
  39. package/dist/engines/VanillaEngine.d.ts.map +1 -0
  40. package/dist/engines/VanillaEngine.js +70 -0
  41. package/dist/engines/VanillaEngine.js.map +1 -0
  42. package/dist/index.d.ts +20 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +89 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/platforms/BedrockServer.d.ts +30 -0
  47. package/dist/platforms/BedrockServer.d.ts.map +1 -0
  48. package/dist/platforms/BedrockServer.js +301 -0
  49. package/dist/platforms/BedrockServer.js.map +1 -0
  50. package/dist/platforms/GeyserBridge.d.ts +9 -0
  51. package/dist/platforms/GeyserBridge.d.ts.map +1 -0
  52. package/dist/platforms/GeyserBridge.js +98 -0
  53. package/dist/platforms/GeyserBridge.js.map +1 -0
  54. package/dist/platforms/JavaServer.d.ts +27 -0
  55. package/dist/platforms/JavaServer.d.ts.map +1 -0
  56. package/dist/platforms/JavaServer.js +237 -0
  57. package/dist/platforms/JavaServer.js.map +1 -0
  58. package/dist/types/index.d.ts +80 -0
  59. package/dist/types/index.d.ts.map +1 -0
  60. package/dist/types/index.js +3 -0
  61. package/dist/types/index.js.map +1 -0
  62. package/dist/utils/FileUtils.d.ts +20 -0
  63. package/dist/utils/FileUtils.d.ts.map +1 -0
  64. package/dist/utils/FileUtils.js +172 -0
  65. package/dist/utils/FileUtils.js.map +1 -0
  66. package/dist/utils/Logger.d.ts +26 -0
  67. package/dist/utils/Logger.d.ts.map +1 -0
  68. package/dist/utils/Logger.js +91 -0
  69. package/dist/utils/Logger.js.map +1 -0
  70. package/dist/utils/PropertiesParser.d.ts +7 -0
  71. package/dist/utils/PropertiesParser.d.ts.map +1 -0
  72. package/dist/utils/PropertiesParser.js +124 -0
  73. package/dist/utils/PropertiesParser.js.map +1 -0
  74. package/dist/utils/SystemDetector.d.ts +14 -0
  75. package/dist/utils/SystemDetector.d.ts.map +1 -0
  76. package/dist/utils/SystemDetector.js +152 -0
  77. package/dist/utils/SystemDetector.js.map +1 -0
  78. package/package.json +51 -0
  79. package/src/core/ConfigHandler.ts +136 -0
  80. package/src/core/JavaChecker.ts +71 -0
  81. package/src/core/MinecraftServer.ts +326 -0
  82. package/src/core/ServerManager.ts +196 -0
  83. package/src/engines/Downloader.ts +144 -0
  84. package/src/engines/FabricEngine.ts +49 -0
  85. package/src/engines/ForgeEngine.ts +65 -0
  86. package/src/engines/PaperEngine.ts +68 -0
  87. package/src/engines/ServerEngine.ts +10 -0
  88. package/src/engines/VanillaEngine.ts +49 -0
  89. package/src/index.ts +83 -0
  90. package/src/platforms/BedrockServer.ts +311 -0
  91. package/src/platforms/GeyserBridge.ts +83 -0
  92. package/src/platforms/JavaServer.ts +241 -0
  93. package/src/scripts/detect-os.sh +56 -0
  94. package/src/scripts/install-java.sh +38 -0
  95. package/src/types/index.ts +89 -0
  96. package/src/utils/FileUtils.ts +162 -0
  97. package/src/utils/Logger.ts +97 -0
  98. package/src/utils/PropertiesParser.ts +126 -0
  99. package/src/utils/SystemDetector.ts +127 -0
  100. package/tsconfig.json +23 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@dimzxzzx07/mc-headless",
3
+ "version": "1.0.0",
4
+ "description": "Minecraft Headless Server Manager - Run Java/Bedrock/Crossplay servers easily via Node.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "dev": "ts-node src/index.ts",
11
+ "test": "jest",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "dependencies": {
15
+ "adm-zip": "^0.5.16",
16
+ "axios": "^1.6.0",
17
+ "chalk": "^4.1.2",
18
+ "fs-extra": "^11.1.1",
19
+ "node-cron": "^3.0.3",
20
+ "pidusage": "^3.0.2",
21
+ "tar": "^7.5.10"
22
+ },
23
+ "devDependencies": {
24
+ "@types/adm-zip": "^0.5.5",
25
+ "@types/fs-extra": "^11.0.4",
26
+ "@types/jest": "^29.5.5",
27
+ "@types/node": "^20.8.0",
28
+ "@types/node-cron": "^3.0.10",
29
+ "@types/pidusage": "^2.0.5",
30
+ "@types/tar": "^6.1.6",
31
+ "jest": "^29.7.0",
32
+ "ts-jest": "^29.1.1",
33
+ "ts-node": "^10.9.1",
34
+ "typescript": "^5.2.2"
35
+ },
36
+ "keywords": [
37
+ "minecraft",
38
+ "server",
39
+ "paper",
40
+ "spigot",
41
+ "forge",
42
+ "fabric",
43
+ "bedrock",
44
+ "geyser",
45
+ "headless",
46
+ "termux",
47
+ "automation"
48
+ ],
49
+ "author": "Dimzxzzx07",
50
+ "license": "UNLICENSED"
51
+ }
@@ -0,0 +1,136 @@
1
+ import { MinecraftConfig } from '../types';
2
+
3
+ export class ConfigHandler {
4
+ private config: MinecraftConfig;
5
+
6
+ constructor(config: Partial<MinecraftConfig>) {
7
+ this.config = this.validateAndComplete(config);
8
+ }
9
+
10
+ private validateAndComplete(config: Partial<MinecraftConfig>): MinecraftConfig {
11
+ const defaultConfig: MinecraftConfig = {
12
+ platform: 'java',
13
+ version: '1.20.1',
14
+ type: 'paper',
15
+ autoAcceptEula: true,
16
+
17
+ memory: {
18
+ init: '1G',
19
+ max: '4G',
20
+ useAikarsFlags: true
21
+ },
22
+
23
+ network: {
24
+ port: 25565,
25
+ bedrockPort: 19132,
26
+ ip: '0.0.0.0',
27
+ onlineMode: false,
28
+ motd: 'Minecraft Server'
29
+ },
30
+
31
+ world: {
32
+ difficulty: 'normal',
33
+ hardcore: false,
34
+ gamemode: 'survival',
35
+ maxPlayers: 20,
36
+ viewDistance: 10,
37
+ levelName: 'world'
38
+ },
39
+
40
+ folders: {
41
+ addons: './addons',
42
+ mods: './mods',
43
+ plugins: './plugins',
44
+ world: './world'
45
+ },
46
+
47
+ autoRestart: false,
48
+ backup: {
49
+ enabled: false,
50
+ interval: '24h',
51
+ path: './backups'
52
+ }
53
+ };
54
+
55
+ const merged = this.mergeDeep(defaultConfig, config) as MinecraftConfig;
56
+
57
+ this.validateConfig(merged);
58
+
59
+ return merged;
60
+ }
61
+
62
+ private validateConfig(config: MinecraftConfig): void {
63
+ if (!['java', 'bedrock', 'all'].includes(config.platform)) {
64
+ throw new Error('Invalid platform. Must be java, bedrock, or all');
65
+ }
66
+
67
+ if (!['paper', 'purpur', 'vanilla', 'spigot', 'forge', 'fabric'].includes(config.type)) {
68
+ throw new Error('Invalid server type');
69
+ }
70
+
71
+ if (!/^\d+\.\d+(\.\d+)?$/.test(config.version)) {
72
+ throw new Error('Invalid version format');
73
+ }
74
+
75
+ const memRegex = /^\d+[MG]$/;
76
+ if (!memRegex.test(config.memory.init) || !memRegex.test(config.memory.max)) {
77
+ throw new Error('Memory must be in format like 1G or 1024M');
78
+ }
79
+
80
+ if (config.network.port < 1 || config.network.port > 65535) {
81
+ throw new Error('Invalid port number');
82
+ }
83
+
84
+ if (config.world.maxPlayers < 1 || config.world.maxPlayers > 1000) {
85
+ throw new Error('Max players must be between 1 and 1000');
86
+ }
87
+
88
+ if (config.world.viewDistance < 3 || config.world.viewDistance > 32) {
89
+ throw new Error('View distance must be between 3 and 32');
90
+ }
91
+ }
92
+
93
+ private mergeDeep(target: any, source: any): any {
94
+ const output = { ...target };
95
+
96
+ if (this.isObject(target) && this.isObject(source)) {
97
+ Object.keys(source).forEach(key => {
98
+ if (this.isObject(source[key])) {
99
+ if (!(key in target)) {
100
+ output[key] = source[key];
101
+ } else {
102
+ output[key] = this.mergeDeep(target[key], source[key]);
103
+ }
104
+ } else {
105
+ output[key] = source[key];
106
+ }
107
+ });
108
+ }
109
+
110
+ return output;
111
+ }
112
+
113
+ private isObject(item: any): boolean {
114
+ return item && typeof item === 'object' && !Array.isArray(item);
115
+ }
116
+
117
+ public getConfig(): MinecraftConfig {
118
+ return this.config;
119
+ }
120
+
121
+ public getServerProperties(): Record<string, any> {
122
+ return {
123
+ 'difficulty': this.config.world.difficulty,
124
+ 'hardcore': this.config.world.hardcore.toString(),
125
+ 'level-seed': this.config.world.seed || '',
126
+ 'gamemode': this.config.world.gamemode,
127
+ 'server-port': this.config.network.port.toString(),
128
+ 'server-ip': this.config.network.ip,
129
+ 'max-players': this.config.world.maxPlayers.toString(),
130
+ 'view-distance': this.config.world.viewDistance.toString(),
131
+ 'level-name': this.config.world.levelName,
132
+ 'motd': this.config.network.motd,
133
+ 'online-mode': this.config.network.onlineMode.toString()
134
+ };
135
+ }
136
+ }
@@ -0,0 +1,71 @@
1
+ import { execSync, exec } from 'child_process';
2
+ import { Logger } from '../utils/Logger';
3
+ import { SystemDetector } from '../utils/SystemDetector';
4
+
5
+ export class JavaChecker {
6
+ private static logger = Logger.getInstance();
7
+
8
+ public static checkJava(): Promise<boolean> {
9
+ return new Promise((resolve) => {
10
+ exec('java -version', (error) => {
11
+ if (error) {
12
+ this.logger.warning('Java is not installed');
13
+ resolve(false);
14
+ } else {
15
+ this.logger.success('Java is installed');
16
+ resolve(true);
17
+ }
18
+ });
19
+ });
20
+ }
21
+
22
+ public static async ensureJava(): Promise<void> {
23
+ const hasJava = await this.checkJava();
24
+
25
+ if (!hasJava) {
26
+ this.logger.warning('Java not found. Attempting to install...');
27
+ await this.installJava();
28
+ }
29
+
30
+ const version = this.getJavaVersion();
31
+ if (version) {
32
+ this.logger.success(`Java version: ${version}`);
33
+ }
34
+ }
35
+
36
+ public static getJavaVersion(): string | null {
37
+ try {
38
+ const output = execSync('java -version 2>&1').toString();
39
+ const match = output.match(/version "([^"]+)"/);
40
+ return match ? match[1] : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ public static async installJava(): Promise<void> {
47
+ const command = SystemDetector.getJavaInstallCommand();
48
+
49
+ this.logger.info(`Installing Java with: ${command}`);
50
+
51
+ return new Promise((resolve, reject) => {
52
+ const child = exec(command, (error) => {
53
+ if (error) {
54
+ this.logger.error('Failed to install Java');
55
+ reject(error);
56
+ } else {
57
+ this.logger.success('Java installed successfully');
58
+ resolve();
59
+ }
60
+ });
61
+
62
+ child.stdout?.on('data', (data) => {
63
+ process.stdout.write(data);
64
+ });
65
+
66
+ child.stderr?.on('data', (data) => {
67
+ process.stderr.write(data);
68
+ });
69
+ });
70
+ }
71
+ }
@@ -0,0 +1,326 @@
1
+ import { EventEmitter } from 'events';
2
+ import { spawn } from 'child_process';
3
+ import pidusage from 'pidusage';
4
+ import * as cron from 'node-cron';
5
+ import { MinecraftConfig, ServerInfo, Player } from '../types';
6
+ import { ConfigHandler } from './ConfigHandler';
7
+ import { JavaChecker } from './JavaChecker';
8
+ import { FileUtils } from '../utils/FileUtils';
9
+ import { Logger } from '../utils/Logger';
10
+ import { SystemDetector } from '../utils/SystemDetector';
11
+ import { PaperEngine } from '../engines/PaperEngine';
12
+ import { VanillaEngine } from '../engines/VanillaEngine';
13
+ import { ForgeEngine } from '../engines/ForgeEngine';
14
+ import { FabricEngine } from '../engines/FabricEngine';
15
+ import { ServerEngine } from '../engines/ServerEngine';
16
+ import { GeyserBridge } from '../platforms/GeyserBridge';
17
+ import * as path from 'path';
18
+
19
+ export class MinecraftServer extends EventEmitter {
20
+ private config: MinecraftConfig;
21
+ private logger: Logger;
22
+ private engine: ServerEngine;
23
+ private geyser: GeyserBridge;
24
+ private process: any = null;
25
+ private serverInfo: ServerInfo;
26
+ private players: Map<string, Player> = new Map();
27
+ private backupCron: cron.ScheduledTask | null = null;
28
+ private startTime: Date | null = null;
29
+
30
+ constructor(userConfig: Partial<MinecraftConfig>) {
31
+ super();
32
+ this.logger = Logger.getInstance();
33
+ this.logger.banner();
34
+
35
+ const handler = new ConfigHandler(userConfig);
36
+ this.config = handler.getConfig();
37
+
38
+ this.engine = this.createEngine();
39
+ this.geyser = new GeyserBridge();
40
+
41
+ this.serverInfo = {
42
+ pid: 0,
43
+ ip: this.config.network.ip,
44
+ port: this.config.network.port,
45
+ bedrockPort: this.config.platform === 'all' ? this.config.network.bedrockPort : undefined,
46
+ version: this.config.version,
47
+ type: this.config.type,
48
+ platform: this.config.platform,
49
+ players: 0,
50
+ maxPlayers: this.config.world.maxPlayers,
51
+ uptime: 0,
52
+ memory: { used: 0, max: 0 },
53
+ status: 'stopped'
54
+ };
55
+ }
56
+
57
+ private createEngine(): ServerEngine {
58
+ switch (this.config.type) {
59
+ case 'paper':
60
+ case 'purpur':
61
+ case 'spigot':
62
+ return new PaperEngine();
63
+ case 'vanilla':
64
+ return new VanillaEngine();
65
+ case 'forge':
66
+ return new ForgeEngine();
67
+ case 'fabric':
68
+ return new FabricEngine();
69
+ default:
70
+ throw new Error(`Unsupported server type: ${this.config.type}`);
71
+ }
72
+ }
73
+
74
+ public async start(): Promise<ServerInfo> {
75
+ this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
76
+
77
+ await JavaChecker.ensureJava();
78
+
79
+ const systemInfo = SystemDetector.getSystemInfo();
80
+ this.logger.debug('System info:', systemInfo);
81
+
82
+ const serverDir = process.cwd();
83
+ await FileUtils.ensureServerStructure(this.config);
84
+
85
+ const jarPath = await this.engine.download(this.config, serverDir);
86
+
87
+ if (this.config.type === 'forge') {
88
+ await this.engine.prepare(this.config, serverDir, jarPath);
89
+ } else {
90
+ await this.engine.prepare(this.config, serverDir);
91
+ }
92
+
93
+ if (this.config.platform === 'all') {
94
+ await this.geyser.setup(this.config);
95
+ }
96
+
97
+ const javaArgs = this.engine.getJavaArgs(this.config);
98
+ const serverJar = this.engine.getServerJar(jarPath);
99
+ const serverArgs = this.engine.getServerArgs();
100
+
101
+ const fullArgs = [
102
+ ...javaArgs,
103
+ '-jar',
104
+ serverJar,
105
+ ...serverArgs
106
+ ];
107
+
108
+ this.logger.info(`Launching: java ${fullArgs.join(' ')}`);
109
+
110
+ this.process = spawn('java', fullArgs, {
111
+ cwd: serverDir,
112
+ stdio: 'pipe'
113
+ });
114
+
115
+ this.serverInfo.pid = this.process.pid!;
116
+ this.serverInfo.status = 'starting';
117
+ this.startTime = new Date();
118
+
119
+ this.process.stdout.on('data', (data: Buffer) => {
120
+ const output = data.toString();
121
+ process.stdout.write(output);
122
+
123
+ if (output.includes('Done') || output.includes('For help, type "help"')) {
124
+ this.serverInfo.status = 'running';
125
+ this.logger.success('Server started successfully!');
126
+ this.emit('ready', this.serverInfo);
127
+ }
128
+
129
+ if (output.includes('joined the game')) {
130
+ const match = output.match(/(\w+) joined the game/);
131
+ if (match) {
132
+ this.handlePlayerJoin(match[1]);
133
+ }
134
+ }
135
+
136
+ if (output.includes('left the game')) {
137
+ const match = output.match(/(\w+) left the game/);
138
+ if (match) {
139
+ this.handlePlayerLeave(match[1]);
140
+ }
141
+ }
142
+ });
143
+
144
+ this.process.stderr.on('data', (data: Buffer) => {
145
+ process.stderr.write(data.toString());
146
+ });
147
+
148
+ this.process.on('exit', (code: number) => {
149
+ this.serverInfo.status = 'stopped';
150
+ this.logger.warning(`Server stopped with code ${code}`);
151
+
152
+ if (this.config.autoRestart && code !== 0) {
153
+ this.logger.info('Auto-restarting...');
154
+ this.start();
155
+ }
156
+
157
+ this.emit('stop', { code });
158
+ });
159
+
160
+ this.monitorResources();
161
+
162
+ if (this.config.backup.enabled) {
163
+ this.setupBackups();
164
+ }
165
+
166
+ return this.serverInfo;
167
+ }
168
+
169
+ public async stop(): Promise<void> {
170
+ if (!this.process) {
171
+ this.logger.warning('Server not running');
172
+ return;
173
+ }
174
+
175
+ this.logger.info('Stopping server...');
176
+ this.serverInfo.status = 'stopping';
177
+
178
+ this.sendCommand('stop');
179
+
180
+ await new Promise(resolve => setTimeout(resolve, 5000));
181
+
182
+ if (this.process) {
183
+ this.process.kill();
184
+ this.process = null;
185
+ }
186
+
187
+ if (this.backupCron) {
188
+ this.backupCron.stop();
189
+ }
190
+
191
+ if (this.config.platform === 'all') {
192
+ this.geyser.stop();
193
+ }
194
+
195
+ this.logger.success('Server stopped');
196
+ }
197
+
198
+ public sendCommand(command: string): void {
199
+ if (!this.process || this.serverInfo.status !== 'running') {
200
+ throw new Error('Server not running');
201
+ }
202
+
203
+ this.process.stdin.write(command + '\n');
204
+ this.logger.debug(`Command sent: ${command}`);
205
+ }
206
+
207
+ public async getInfo(): Promise<ServerInfo> {
208
+ if (this.serverInfo.status === 'running' && this.process) {
209
+ try {
210
+ const stats = await pidusage(this.process.pid);
211
+ this.serverInfo.memory = {
212
+ used: Math.round(stats.memory / 1024 / 1024),
213
+ max: this.parseMemory(this.config.memory.max)
214
+ };
215
+ this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
216
+ } catch {}
217
+ }
218
+
219
+ return this.serverInfo;
220
+ }
221
+
222
+ public getPlayers(): Player[] {
223
+ return Array.from(this.players.values());
224
+ }
225
+
226
+ public async backup(type: 'full' | 'world' | 'plugins' = 'full'): Promise<string> {
227
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
228
+ const backupName = `backup-${type}-${timestamp}`;
229
+ const backupPath = path.join(this.config.backup.path, backupName);
230
+
231
+ this.logger.info(`Creating ${type} backup...`);
232
+
233
+ switch (type) {
234
+ case 'full':
235
+ await FileUtils.createBackup(process.cwd(), backupPath);
236
+ break;
237
+ case 'world':
238
+ await FileUtils.createBackup(this.config.folders.world, backupPath);
239
+ break;
240
+ case 'plugins':
241
+ await FileUtils.createBackup(this.config.folders.plugins, backupPath);
242
+ break;
243
+ }
244
+
245
+ this.logger.success(`Backup created: ${backupPath}`);
246
+ return backupPath;
247
+ }
248
+
249
+ private monitorResources(): void {
250
+ setInterval(async () => {
251
+ if (this.serverInfo.status === 'running' && this.process) {
252
+ try {
253
+ const stats = await pidusage(this.process.pid);
254
+ this.serverInfo.memory = {
255
+ used: Math.round(stats.memory / 1024 / 1024),
256
+ max: this.parseMemory(this.config.memory.max)
257
+ };
258
+ this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
259
+
260
+ if (stats.cpu > 80) {
261
+ this.logger.warning(`High CPU usage: ${stats.cpu}%`);
262
+ }
263
+
264
+ this.emit('resource', this.serverInfo);
265
+ } catch {}
266
+ }
267
+ }, 5000);
268
+ }
269
+
270
+ private setupBackups(): void {
271
+ const cronExpression = this.convertIntervalToCron(this.config.backup.interval);
272
+
273
+ if (cronExpression) {
274
+ this.backupCron = cron.schedule(cronExpression, () => {
275
+ this.backup('world');
276
+ });
277
+ this.logger.info(`Backups scheduled: ${this.config.backup.interval}`);
278
+ }
279
+ }
280
+
281
+ private convertIntervalToCron(interval: string): string | null {
282
+ const match = interval.match(/(\d+)([hms])/);
283
+ if (!match) return null;
284
+
285
+ const value = parseInt(match[1]);
286
+ const unit = match[2];
287
+
288
+ switch (unit) {
289
+ case 'h':
290
+ return `0 */${value} * * *`;
291
+ case 'm':
292
+ return `*/${value} * * * *`;
293
+ case 's':
294
+ return `*/${value} * * * * *`;
295
+ default:
296
+ return null;
297
+ }
298
+ }
299
+
300
+ private handlePlayerJoin(name: string): void {
301
+ const player: Player = {
302
+ name,
303
+ uuid: '',
304
+ ip: '',
305
+ ping: 0,
306
+ connectedAt: new Date()
307
+ };
308
+ this.players.set(name, player);
309
+ this.serverInfo.players = this.players.size;
310
+ this.emit('player-join', player);
311
+ }
312
+
313
+ private handlePlayerLeave(name: string): void {
314
+ this.players.delete(name);
315
+ this.serverInfo.players = this.players.size;
316
+ this.emit('player-leave', name);
317
+ }
318
+
319
+ private parseMemory(memStr: string): number {
320
+ const value = parseInt(memStr);
321
+ if (memStr.endsWith('G')) {
322
+ return value * 1024;
323
+ }
324
+ return value;
325
+ }
326
+ }