@minecraft-docker/mcctl 1.6.0 → 1.6.1
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.
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +5 -0
- package/dist/application/index.js.map +1 -0
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +182 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/ban.d.ts +14 -0
- package/dist/commands/ban.d.ts.map +1 -0
- package/dist/commands/ban.js +418 -0
- package/dist/commands/ban.js.map +1 -0
- package/dist/commands/config.d.ts +18 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +106 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts +22 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +83 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/delete.d.ts +17 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +71 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/exec.d.ts +10 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +29 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +18 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +186 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/kick.d.ts +12 -0
- package/dist/commands/kick.d.ts.map +1 -0
- package/dist/commands/kick.js +71 -0
- package/dist/commands/kick.js.map +1 -0
- package/dist/commands/migrate.d.ts +18 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +470 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/op.d.ts +12 -0
- package/dist/commands/op.d.ts.map +1 -0
- package/dist/commands/op.js +220 -0
- package/dist/commands/op.js.map +1 -0
- package/dist/commands/player-online.d.ts +11 -0
- package/dist/commands/player-online.d.ts.map +1 -0
- package/dist/commands/player-online.js +151 -0
- package/dist/commands/player-online.js.map +1 -0
- package/dist/commands/player.d.ts +18 -0
- package/dist/commands/player.d.ts.map +1 -0
- package/dist/commands/player.js +352 -0
- package/dist/commands/player.js.map +1 -0
- package/dist/commands/server-backup.d.ts +36 -0
- package/dist/commands/server-backup.d.ts.map +1 -0
- package/dist/commands/server-backup.js +320 -0
- package/dist/commands/server-backup.js.map +1 -0
- package/dist/commands/server-restore.d.ts +13 -0
- package/dist/commands/server-restore.d.ts.map +1 -0
- package/dist/commands/server-restore.js +294 -0
- package/dist/commands/server-restore.js.map +1 -0
- package/dist/commands/status.d.ts +13 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +325 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/whitelist.d.ts +12 -0
- package/dist/commands/whitelist.d.ts.map +1 -0
- package/dist/commands/whitelist.js +316 -0
- package/dist/commands/whitelist.js.map +1 -0
- package/dist/commands/world.d.ts +16 -0
- package/dist/commands/world.d.ts.map +1 -0
- package/dist/commands/world.js +135 -0
- package/dist/commands/world.js.map +1 -0
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +7 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +609 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts +29 -0
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts.map +1 -0
- package/dist/infrastructure/adapters/ClackPromptAdapter.js +419 -0
- package/dist/infrastructure/adapters/ClackPromptAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/index.d.ts +3 -0
- package/dist/infrastructure/adapters/index.d.ts.map +1 -0
- package/dist/infrastructure/adapters/index.js +5 -0
- package/dist/infrastructure/adapters/index.js.map +1 -0
- package/dist/infrastructure/di/container.d.ts +47 -0
- package/dist/infrastructure/di/container.d.ts.map +1 -0
- package/dist/infrastructure/di/container.js +140 -0
- package/dist/infrastructure/di/container.js.map +1 -0
- package/dist/infrastructure/di/index.d.ts +2 -0
- package/dist/infrastructure/di/index.d.ts.map +1 -0
- package/dist/infrastructure/di/index.js +2 -0
- package/dist/infrastructure/di/index.js.map +1 -0
- package/dist/infrastructure/index.d.ts +4 -0
- package/dist/infrastructure/index.d.ts.map +1 -0
- package/dist/infrastructure/index.js +7 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/lib/mojang-api.d.ts +51 -0
- package/dist/lib/mojang-api.d.ts.map +1 -0
- package/dist/lib/mojang-api.js +136 -0
- package/dist/lib/mojang-api.js.map +1 -0
- package/dist/lib/player-cache.d.ts +79 -0
- package/dist/lib/player-cache.d.ts.map +1 -0
- package/dist/lib/player-cache.js +302 -0
- package/dist/lib/player-cache.js.map +1 -0
- package/dist/lib/player-json.d.ts +69 -0
- package/dist/lib/player-json.d.ts.map +1 -0
- package/dist/lib/player-json.js +96 -0
- package/dist/lib/player-json.js.map +1 -0
- package/dist/lib/prompts/action-select.d.ts +23 -0
- package/dist/lib/prompts/action-select.d.ts.map +1 -0
- package/dist/lib/prompts/action-select.js +124 -0
- package/dist/lib/prompts/action-select.js.map +1 -0
- package/dist/lib/prompts/index.d.ts +7 -0
- package/dist/lib/prompts/index.d.ts.map +1 -0
- package/dist/lib/prompts/index.js +7 -0
- package/dist/lib/prompts/index.js.map +1 -0
- package/dist/lib/prompts/player-select.d.ts +25 -0
- package/dist/lib/prompts/player-select.d.ts.map +1 -0
- package/dist/lib/prompts/player-select.js +122 -0
- package/dist/lib/prompts/player-select.js.map +1 -0
- package/dist/lib/prompts/server-select.d.ts +26 -0
- package/dist/lib/prompts/server-select.d.ts.map +1 -0
- package/dist/lib/prompts/server-select.js +99 -0
- package/dist/lib/prompts/server-select.js.map +1 -0
- package/dist/lib/rcon.d.ts +53 -0
- package/dist/lib/rcon.d.ts.map +1 -0
- package/dist/lib/rcon.js +131 -0
- package/dist/lib/rcon.js.map +1 -0
- package/dist/lib/shell.d.ts +109 -0
- package/dist/lib/shell.d.ts.map +1 -0
- package/dist/lib/shell.js +318 -0
- package/dist/lib/shell.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infrastructure/di/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAyB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ClackPromptAdapter } from './adapters/ClackPromptAdapter.js';
|
|
2
|
+
export { ShellAdapter, ServerRepository, WorldRepository, DocsAdapter, } from '@minecraft-docker/shared';
|
|
3
|
+
export { Container, getContainer, resetContainer } from './di/index.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAGtE,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// CLI-specific adapter
|
|
2
|
+
export { ClackPromptAdapter } from './adapters/ClackPromptAdapter.js';
|
|
3
|
+
// Re-export shared adapters for backward compatibility
|
|
4
|
+
export { ShellAdapter, ServerRepository, WorldRepository, DocsAdapter, } from '@minecraft-docker/shared';
|
|
5
|
+
// DI Container (CLI-specific)
|
|
6
|
+
export { Container, getContainer, resetContainer } from './di/index.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infrastructure/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,uDAAuD;AACvD,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAElC,8BAA8B;AAC9B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mojang API Client
|
|
3
|
+
* Provides player lookup functionality via Mojang API
|
|
4
|
+
*/
|
|
5
|
+
export interface MojangProfile {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PlayerInfo {
|
|
10
|
+
name: string;
|
|
11
|
+
uuid: string;
|
|
12
|
+
uuidNoDashes: string;
|
|
13
|
+
skinUrl?: string;
|
|
14
|
+
isOnline?: boolean;
|
|
15
|
+
server?: string;
|
|
16
|
+
source: 'mojang' | 'offline' | 'cache';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mojang API client for player lookup
|
|
20
|
+
*/
|
|
21
|
+
export declare class MojangApiClient {
|
|
22
|
+
private static readonly API_BASE;
|
|
23
|
+
private static readonly SESSION_BASE;
|
|
24
|
+
/**
|
|
25
|
+
* Look up a player by username via Mojang API
|
|
26
|
+
* Returns null if player not found
|
|
27
|
+
*/
|
|
28
|
+
lookupByUsername(username: string): Promise<PlayerInfo | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Get detailed profile including skin URL
|
|
31
|
+
* Requires UUID (with or without dashes)
|
|
32
|
+
*/
|
|
33
|
+
getProfile(uuid: string): Promise<PlayerInfo | null>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Format UUID with dashes
|
|
37
|
+
* Input: 8667ba71b85a4004af54457a9734eed7
|
|
38
|
+
* Output: 8667ba71-b85a-4004-af54-457a9734eed7
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatUuid(uuid: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate offline UUID from username
|
|
43
|
+
* Offline servers use a different UUID format based on "OfflinePlayer:" + username
|
|
44
|
+
*/
|
|
45
|
+
export declare function calculateOfflineUuid(username: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Get player info for offline mode
|
|
48
|
+
*/
|
|
49
|
+
export declare function getOfflinePlayerInfo(username: string): PlayerInfo;
|
|
50
|
+
export declare function getMojangApiClient(): MojangApiClient;
|
|
51
|
+
//# sourceMappingURL=mojang-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mojang-api.d.ts","sourceRoot":"","sources":["../../src/lib/mojang-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;CACxC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAC5D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAsC;IAE1E;;;OAGG;IACG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA+BpE;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;CAkD3D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc7D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQjE;AAKD,wBAAgB,kBAAkB,IAAI,eAAe,CAKpD"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mojang API Client
|
|
3
|
+
* Provides player lookup functionality via Mojang API
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
/**
|
|
7
|
+
* Mojang API client for player lookup
|
|
8
|
+
*/
|
|
9
|
+
export class MojangApiClient {
|
|
10
|
+
static API_BASE = 'https://api.mojang.com';
|
|
11
|
+
static SESSION_BASE = 'https://sessionserver.mojang.com';
|
|
12
|
+
/**
|
|
13
|
+
* Look up a player by username via Mojang API
|
|
14
|
+
* Returns null if player not found
|
|
15
|
+
*/
|
|
16
|
+
async lookupByUsername(username) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${MojangApiClient.API_BASE}/users/profiles/minecraft/${encodeURIComponent(username)}`);
|
|
19
|
+
if (response.status === 404) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Mojang API error: ${response.status} ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const profile = await response.json();
|
|
26
|
+
const uuid = formatUuid(profile.id);
|
|
27
|
+
return {
|
|
28
|
+
name: profile.name,
|
|
29
|
+
uuid,
|
|
30
|
+
uuidNoDashes: profile.id,
|
|
31
|
+
source: 'mojang',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof Error && error.message.includes('fetch')) {
|
|
36
|
+
throw new Error(`Failed to connect to Mojang API: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get detailed profile including skin URL
|
|
43
|
+
* Requires UUID (with or without dashes)
|
|
44
|
+
*/
|
|
45
|
+
async getProfile(uuid) {
|
|
46
|
+
try {
|
|
47
|
+
const uuidNoDashes = uuid.replace(/-/g, '');
|
|
48
|
+
const response = await fetch(`${MojangApiClient.SESSION_BASE}/session/minecraft/profile/${uuidNoDashes}`);
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`Mojang Session API error: ${response.status} ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
const formattedUuid = formatUuid(data.id);
|
|
57
|
+
// Extract skin URL from properties
|
|
58
|
+
let skinUrl;
|
|
59
|
+
if (data.properties) {
|
|
60
|
+
const texturesProp = data.properties.find((p) => p.name === 'textures');
|
|
61
|
+
if (texturesProp) {
|
|
62
|
+
try {
|
|
63
|
+
const textures = JSON.parse(Buffer.from(texturesProp.value, 'base64').toString('utf-8'));
|
|
64
|
+
skinUrl = textures.textures?.SKIN?.url;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Ignore texture parsing errors
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
name: data.name,
|
|
73
|
+
uuid: formattedUuid,
|
|
74
|
+
uuidNoDashes: data.id,
|
|
75
|
+
skinUrl,
|
|
76
|
+
source: 'mojang',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof Error && error.message.includes('fetch')) {
|
|
81
|
+
throw new Error(`Failed to connect to Mojang Session API: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Format UUID with dashes
|
|
89
|
+
* Input: 8667ba71b85a4004af54457a9734eed7
|
|
90
|
+
* Output: 8667ba71-b85a-4004-af54-457a9734eed7
|
|
91
|
+
*/
|
|
92
|
+
export function formatUuid(uuid) {
|
|
93
|
+
const clean = uuid.replace(/-/g, '');
|
|
94
|
+
if (clean.length !== 32) {
|
|
95
|
+
return uuid; // Return as-is if not a valid UUID
|
|
96
|
+
}
|
|
97
|
+
return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculate offline UUID from username
|
|
101
|
+
* Offline servers use a different UUID format based on "OfflinePlayer:" + username
|
|
102
|
+
*/
|
|
103
|
+
export function calculateOfflineUuid(username) {
|
|
104
|
+
const hash = createHash('md5').update(`OfflinePlayer:${username}`).digest();
|
|
105
|
+
// Set version to 3 (name-based MD5)
|
|
106
|
+
const byte6 = hash[6];
|
|
107
|
+
const byte8 = hash[8];
|
|
108
|
+
if (byte6 !== undefined && byte8 !== undefined) {
|
|
109
|
+
hash[6] = (byte6 & 0x0f) | 0x30;
|
|
110
|
+
// Set variant to RFC 4122
|
|
111
|
+
hash[8] = (byte8 & 0x3f) | 0x80;
|
|
112
|
+
}
|
|
113
|
+
const hex = hash.toString('hex');
|
|
114
|
+
return formatUuid(hex);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get player info for offline mode
|
|
118
|
+
*/
|
|
119
|
+
export function getOfflinePlayerInfo(username) {
|
|
120
|
+
const uuid = calculateOfflineUuid(username);
|
|
121
|
+
return {
|
|
122
|
+
name: username,
|
|
123
|
+
uuid,
|
|
124
|
+
uuidNoDashes: uuid.replace(/-/g, ''),
|
|
125
|
+
source: 'offline',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Singleton instance
|
|
129
|
+
let mojangApiClient = null;
|
|
130
|
+
export function getMojangApiClient() {
|
|
131
|
+
if (!mojangApiClient) {
|
|
132
|
+
mojangApiClient = new MojangApiClient();
|
|
133
|
+
}
|
|
134
|
+
return mojangApiClient;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=mojang-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mojang-api.js","sourceRoot":"","sources":["../../src/lib/mojang-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Player Cache System
|
|
3
|
+
* Encrypted local cache for Mojang API responses to minimize rate limiting
|
|
4
|
+
*/
|
|
5
|
+
import { type PlayerInfo } from './mojang-api.js';
|
|
6
|
+
/**
|
|
7
|
+
* Player cache with AES-256-GCM encryption
|
|
8
|
+
*/
|
|
9
|
+
export declare class PlayerCache {
|
|
10
|
+
private readonly cachePath;
|
|
11
|
+
private cache;
|
|
12
|
+
private encryptionKey;
|
|
13
|
+
constructor(cacheDir?: string);
|
|
14
|
+
/**
|
|
15
|
+
* Look up a player by username
|
|
16
|
+
* First checks cache, then Mojang API
|
|
17
|
+
*/
|
|
18
|
+
lookup(username: string, options?: {
|
|
19
|
+
forceRefresh?: boolean;
|
|
20
|
+
offline?: boolean;
|
|
21
|
+
}): Promise<PlayerInfo | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Look up a player with full profile (including skin URL)
|
|
24
|
+
*/
|
|
25
|
+
lookupWithProfile(username: string, options?: {
|
|
26
|
+
forceRefresh?: boolean;
|
|
27
|
+
}): Promise<PlayerInfo | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Get player from cache if valid
|
|
30
|
+
*/
|
|
31
|
+
private getFromCache;
|
|
32
|
+
/**
|
|
33
|
+
* Save player info to cache
|
|
34
|
+
*/
|
|
35
|
+
private saveToCache;
|
|
36
|
+
/**
|
|
37
|
+
* Update skin URL in cache
|
|
38
|
+
*/
|
|
39
|
+
private updateSkinInCache;
|
|
40
|
+
/**
|
|
41
|
+
* Load cache from encrypted file
|
|
42
|
+
*/
|
|
43
|
+
private loadCache;
|
|
44
|
+
/**
|
|
45
|
+
* Persist cache to encrypted file
|
|
46
|
+
*/
|
|
47
|
+
private persistCache;
|
|
48
|
+
/**
|
|
49
|
+
* Get or derive encryption key
|
|
50
|
+
*/
|
|
51
|
+
private getEncryptionKey;
|
|
52
|
+
/**
|
|
53
|
+
* Get machine-specific identifier for key derivation
|
|
54
|
+
*/
|
|
55
|
+
private getMachineIdentifier;
|
|
56
|
+
/**
|
|
57
|
+
* Encrypt data using AES-256-GCM
|
|
58
|
+
*/
|
|
59
|
+
private encrypt;
|
|
60
|
+
/**
|
|
61
|
+
* Decrypt data using AES-256-GCM
|
|
62
|
+
*/
|
|
63
|
+
private decrypt;
|
|
64
|
+
/**
|
|
65
|
+
* Clear all cached data
|
|
66
|
+
*/
|
|
67
|
+
clear(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Get cache statistics
|
|
70
|
+
*/
|
|
71
|
+
getStats(): Promise<{
|
|
72
|
+
playerCount: number;
|
|
73
|
+
oldestEntry: number | null;
|
|
74
|
+
newestEntry: number | null;
|
|
75
|
+
cacheSize: number;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
export declare function getPlayerCache(cacheDir?: string): PlayerCache;
|
|
79
|
+
//# sourceMappingURL=player-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-cache.d.ts","sourceRoot":"","sources":["../../src/lib/player-cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AAoCzB;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,aAAa,CAAuB;gBAEhC,QAAQ,CAAC,EAAE,MAAM;IAK7B;;;OAGG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAkCvH;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAqC/G;;OAEG;YACW,YAAY;IAsB1B;;OAEG;YACW,WAAW;IAuBzB;;OAEG;YACW,iBAAiB;IAY/B;;OAEG;YACW,SAAS;IAyBvB;;OAEG;YACW,YAAY;IAwB1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;OAEG;YACW,OAAO;IAoBrB;;OAEG;YACW,OAAO;IAkBrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CAsBH;AAKD,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAK7D"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Player Cache System
|
|
3
|
+
* Encrypted local cache for Mojang API responses to minimize rate limiting
|
|
4
|
+
*/
|
|
5
|
+
import { createCipheriv, createDecipheriv, randomBytes, createHash, pbkdf2Sync } from 'node:crypto';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, statSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { homedir, hostname, platform, userInfo } from 'node:os';
|
|
9
|
+
import { getMojangApiClient, getOfflinePlayerInfo, } from './mojang-api.js';
|
|
10
|
+
const CACHE_VERSION = 1;
|
|
11
|
+
const CACHE_FILENAME = '.player-cache';
|
|
12
|
+
const ENCRYPTION_ALGORITHM = 'aes-256-gcm';
|
|
13
|
+
// Cache duration constants (in milliseconds)
|
|
14
|
+
const CACHE_DURATION = {
|
|
15
|
+
UUID: Infinity, // UUID never changes
|
|
16
|
+
USERNAME: 30 * 24 * 60 * 60 * 1000, // 30 days (name changes allowed)
|
|
17
|
+
SKIN: 24 * 60 * 60 * 1000, // 1 day (changes frequently)
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Player cache with AES-256-GCM encryption
|
|
21
|
+
*/
|
|
22
|
+
export class PlayerCache {
|
|
23
|
+
cachePath;
|
|
24
|
+
cache = null;
|
|
25
|
+
encryptionKey = null;
|
|
26
|
+
constructor(cacheDir) {
|
|
27
|
+
const baseDir = cacheDir ?? join(homedir(), '.mcctl');
|
|
28
|
+
this.cachePath = join(baseDir, CACHE_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Look up a player by username
|
|
32
|
+
* First checks cache, then Mojang API
|
|
33
|
+
*/
|
|
34
|
+
async lookup(username, options = {}) {
|
|
35
|
+
// Offline mode uses calculated UUID
|
|
36
|
+
if (options.offline) {
|
|
37
|
+
return getOfflinePlayerInfo(username);
|
|
38
|
+
}
|
|
39
|
+
const lowerUsername = username.toLowerCase();
|
|
40
|
+
// Check cache first (unless force refresh)
|
|
41
|
+
if (!options.forceRefresh) {
|
|
42
|
+
const cached = await this.getFromCache(lowerUsername);
|
|
43
|
+
if (cached) {
|
|
44
|
+
return {
|
|
45
|
+
name: cached.name,
|
|
46
|
+
uuid: cached.uuid,
|
|
47
|
+
uuidNoDashes: cached.uuidNoDashes,
|
|
48
|
+
skinUrl: cached.skinUrl,
|
|
49
|
+
source: 'cache',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Fetch from Mojang API
|
|
54
|
+
const client = getMojangApiClient();
|
|
55
|
+
const info = await client.lookupByUsername(username);
|
|
56
|
+
if (info) {
|
|
57
|
+
// Save to cache
|
|
58
|
+
await this.saveToCache(info);
|
|
59
|
+
}
|
|
60
|
+
return info;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Look up a player with full profile (including skin URL)
|
|
64
|
+
*/
|
|
65
|
+
async lookupWithProfile(username, options = {}) {
|
|
66
|
+
const basic = await this.lookup(username, options);
|
|
67
|
+
if (!basic) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Check if we need to fetch skin URL
|
|
71
|
+
const lowerUsername = username.toLowerCase();
|
|
72
|
+
const cached = await this.getFromCache(lowerUsername);
|
|
73
|
+
// Check if skin cache is still valid
|
|
74
|
+
const skinValid = cached?.skinUpdatedAt &&
|
|
75
|
+
(Date.now() - cached.skinUpdatedAt) < CACHE_DURATION.SKIN;
|
|
76
|
+
if (!options.forceRefresh && skinValid && cached?.skinUrl) {
|
|
77
|
+
return {
|
|
78
|
+
...basic,
|
|
79
|
+
skinUrl: cached.skinUrl,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Fetch full profile from Mojang
|
|
83
|
+
const client = getMojangApiClient();
|
|
84
|
+
const profile = await client.getProfile(basic.uuid);
|
|
85
|
+
if (profile?.skinUrl) {
|
|
86
|
+
// Update cache with skin URL
|
|
87
|
+
await this.updateSkinInCache(lowerUsername, profile.skinUrl);
|
|
88
|
+
return {
|
|
89
|
+
...basic,
|
|
90
|
+
skinUrl: profile.skinUrl,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return basic;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get player from cache if valid
|
|
97
|
+
*/
|
|
98
|
+
async getFromCache(lowerUsername) {
|
|
99
|
+
await this.loadCache();
|
|
100
|
+
if (!this.cache) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const cached = this.cache.players[lowerUsername];
|
|
104
|
+
if (!cached) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// Check if name cache is still valid
|
|
108
|
+
const nameAge = Date.now() - cached.nameUpdatedAt;
|
|
109
|
+
if (nameAge > CACHE_DURATION.USERNAME) {
|
|
110
|
+
// Name cache expired, but UUID is still valid
|
|
111
|
+
// Return null to trigger API call, but UUID will be updated in place
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Save player info to cache
|
|
118
|
+
*/
|
|
119
|
+
async saveToCache(info) {
|
|
120
|
+
await this.loadCache();
|
|
121
|
+
if (!this.cache) {
|
|
122
|
+
this.cache = { version: CACHE_VERSION, players: {} };
|
|
123
|
+
}
|
|
124
|
+
const lowerUsername = info.name.toLowerCase();
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
this.cache.players[lowerUsername] = {
|
|
127
|
+
name: info.name,
|
|
128
|
+
uuid: info.uuid,
|
|
129
|
+
uuidNoDashes: info.uuidNoDashes,
|
|
130
|
+
skinUrl: info.skinUrl,
|
|
131
|
+
source: info.source === 'cache' ? 'mojang' : info.source,
|
|
132
|
+
cachedAt: now,
|
|
133
|
+
nameUpdatedAt: now,
|
|
134
|
+
skinUpdatedAt: info.skinUrl ? now : undefined,
|
|
135
|
+
};
|
|
136
|
+
await this.persistCache();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Update skin URL in cache
|
|
140
|
+
*/
|
|
141
|
+
async updateSkinInCache(lowerUsername, skinUrl) {
|
|
142
|
+
await this.loadCache();
|
|
143
|
+
if (!this.cache || !this.cache.players[lowerUsername]) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.cache.players[lowerUsername].skinUrl = skinUrl;
|
|
147
|
+
this.cache.players[lowerUsername].skinUpdatedAt = Date.now();
|
|
148
|
+
await this.persistCache();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load cache from encrypted file
|
|
152
|
+
*/
|
|
153
|
+
async loadCache() {
|
|
154
|
+
if (this.cache !== null) {
|
|
155
|
+
return; // Already loaded
|
|
156
|
+
}
|
|
157
|
+
if (!existsSync(this.cachePath)) {
|
|
158
|
+
this.cache = { version: CACHE_VERSION, players: {} };
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const encrypted = JSON.parse(readFileSync(this.cachePath, 'utf-8'));
|
|
163
|
+
const decrypted = await this.decrypt(encrypted);
|
|
164
|
+
this.cache = JSON.parse(decrypted);
|
|
165
|
+
// Migrate old cache versions if needed
|
|
166
|
+
if (this.cache.version !== CACHE_VERSION) {
|
|
167
|
+
this.cache = { version: CACHE_VERSION, players: {} };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// If decryption fails, start fresh
|
|
172
|
+
this.cache = { version: CACHE_VERSION, players: {} };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Persist cache to encrypted file
|
|
177
|
+
*/
|
|
178
|
+
async persistCache() {
|
|
179
|
+
if (!this.cache) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Ensure directory exists
|
|
183
|
+
const dir = dirname(this.cachePath);
|
|
184
|
+
if (!existsSync(dir)) {
|
|
185
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
186
|
+
}
|
|
187
|
+
const data = JSON.stringify(this.cache);
|
|
188
|
+
const encrypted = await this.encrypt(data);
|
|
189
|
+
writeFileSync(this.cachePath, JSON.stringify(encrypted), { mode: 0o600 });
|
|
190
|
+
// Ensure file permissions are correct (owner read/write only)
|
|
191
|
+
try {
|
|
192
|
+
chmodSync(this.cachePath, 0o600);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Ignore permission errors on some systems
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get or derive encryption key
|
|
200
|
+
*/
|
|
201
|
+
getEncryptionKey(salt) {
|
|
202
|
+
if (this.encryptionKey) {
|
|
203
|
+
return this.encryptionKey;
|
|
204
|
+
}
|
|
205
|
+
// Derive key from machine-specific identifiers
|
|
206
|
+
const machineId = this.getMachineIdentifier();
|
|
207
|
+
this.encryptionKey = pbkdf2Sync(machineId, salt, 100000, 32, 'sha256');
|
|
208
|
+
return this.encryptionKey;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get machine-specific identifier for key derivation
|
|
212
|
+
*/
|
|
213
|
+
getMachineIdentifier() {
|
|
214
|
+
// Combine multiple machine-specific values
|
|
215
|
+
const parts = [
|
|
216
|
+
hostname(),
|
|
217
|
+
platform(),
|
|
218
|
+
userInfo().username,
|
|
219
|
+
homedir(),
|
|
220
|
+
// Add more entropy from environment
|
|
221
|
+
process.env.USER || process.env.USERNAME || '',
|
|
222
|
+
];
|
|
223
|
+
// Hash for consistent length
|
|
224
|
+
return createHash('sha256').update(parts.join('|')).digest('hex');
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Encrypt data using AES-256-GCM
|
|
228
|
+
*/
|
|
229
|
+
async encrypt(data) {
|
|
230
|
+
const salt = randomBytes(16);
|
|
231
|
+
const key = this.getEncryptionKey(salt);
|
|
232
|
+
const iv = randomBytes(12);
|
|
233
|
+
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
234
|
+
const encrypted = Buffer.concat([
|
|
235
|
+
cipher.update(data, 'utf-8'),
|
|
236
|
+
cipher.final(),
|
|
237
|
+
]);
|
|
238
|
+
const tag = cipher.getAuthTag();
|
|
239
|
+
return {
|
|
240
|
+
iv: iv.toString('base64'),
|
|
241
|
+
tag: tag.toString('base64'),
|
|
242
|
+
data: encrypted.toString('base64'),
|
|
243
|
+
salt: salt.toString('base64'),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Decrypt data using AES-256-GCM
|
|
248
|
+
*/
|
|
249
|
+
async decrypt(encrypted) {
|
|
250
|
+
const salt = Buffer.from(encrypted.salt, 'base64');
|
|
251
|
+
const key = this.getEncryptionKey(salt);
|
|
252
|
+
const iv = Buffer.from(encrypted.iv, 'base64');
|
|
253
|
+
const tag = Buffer.from(encrypted.tag, 'base64');
|
|
254
|
+
const data = Buffer.from(encrypted.data, 'base64');
|
|
255
|
+
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
256
|
+
decipher.setAuthTag(tag);
|
|
257
|
+
const decrypted = Buffer.concat([
|
|
258
|
+
decipher.update(data),
|
|
259
|
+
decipher.final(),
|
|
260
|
+
]);
|
|
261
|
+
return decrypted.toString('utf-8');
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Clear all cached data
|
|
265
|
+
*/
|
|
266
|
+
async clear() {
|
|
267
|
+
this.cache = { version: CACHE_VERSION, players: {} };
|
|
268
|
+
await this.persistCache();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get cache statistics
|
|
272
|
+
*/
|
|
273
|
+
async getStats() {
|
|
274
|
+
await this.loadCache();
|
|
275
|
+
const players = Object.values(this.cache?.players ?? {});
|
|
276
|
+
const timestamps = players.map(p => p.cachedAt);
|
|
277
|
+
let cacheSize = 0;
|
|
278
|
+
try {
|
|
279
|
+
if (existsSync(this.cachePath)) {
|
|
280
|
+
cacheSize = statSync(this.cachePath).size;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Ignore stat errors
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
playerCount: players.length,
|
|
288
|
+
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : null,
|
|
289
|
+
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : null,
|
|
290
|
+
cacheSize,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Singleton instance
|
|
295
|
+
let playerCache = null;
|
|
296
|
+
export function getPlayerCache(cacheDir) {
|
|
297
|
+
if (!playerCache) {
|
|
298
|
+
playerCache = new PlayerCache(cacheDir);
|
|
299
|
+
}
|
|
300
|
+
return playerCache;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=player-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-cache.js","sourceRoot":"","sources":["../../src/lib/player-cache.ts"],"names":[],"mappings":"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"}
|