///
import { EventEmitter } from 'events';
import { Socket, Server as NetServer } from 'net';
// ═══════════════════════════════════════════════════════════════════════════
// Version
// ═══════════════════════════════════════════════════════════════════════════
export const VERSION: string;
export function getVersion(): string;
// ═══════════════════════════════════════════════════════════════════════════
// Protocol constants
// ═══════════════════════════════════════════════════════════════════════════
export const BLOCKS: Readonly<{
AIR: 0; STONE: 1; GRASS: 2; DIRT: 3; COBBLESTONE: 4; WOOD_PLANKS: 5;
SAPLING: 6; BEDROCK: 7; WATER_FLOWING: 8; WATER: 9; LAVA_FLOWING: 10;
LAVA: 11; SAND: 12; GRAVEL: 13; GOLD_ORE: 14; IRON_ORE: 15; COAL_ORE: 16;
LOG: 17; LEAVES: 18; SPONGE: 19; GLASS: 20;
RED_CLOTH: 21; ORANGE_CLOTH: 22; YELLOW_CLOTH: 23; LIME_CLOTH: 24;
GREEN_CLOTH: 25; TEAL_CLOTH: 26; AQUA_CLOTH: 27; CYAN_CLOTH: 28;
BLUE_CLOTH: 29; INDIGO_CLOTH: 30; VIOLET_CLOTH: 31; MAGENTA_CLOTH: 32;
PINK_CLOTH: 33; BLACK_CLOTH: 34; GRAY_CLOTH: 35; WHITE_CLOTH: 36;
DANDELION: 37; ROSE: 38; BROWN_MUSHROOM: 39; RED_MUSHROOM: 40;
GOLD_BLOCK: 41; IRON_BLOCK: 42; DOUBLE_SLAB: 43; SLAB: 44;
BRICK: 45; TNT: 46; BOOKSHELF: 47; MOSSY_COBBLE: 48; OBSIDIAN: 49;
}>;
export const BLOCK_NAMES: Readonly>;
export const BLOCK_MODE: Readonly<{ DESTROY: 0x00; CREATE: 0x01 }>;
export const USER_TYPE: Readonly<{ NORMAL: 0x00; OP: 0x64 }>;
/** Send as `unused` byte in client identification to signal CPE support. */
export const CPE_MAGIC: 0x42;
// ═══════════════════════════════════════════════════════════════════════════
// Client
// ═══════════════════════════════════════════════════════════════════════════
export interface ClientOptions {
host?: string;
port?: number;
/** Milliseconds before giving up on TCP connect (default: 10 000) */
connectTimeout?: number;
/** Milliseconds between emitting 'ping' events; 0 = disabled (default: 0) */
pingInterval?: number;
}
export class ClassiCubeClient extends EventEmitter {
constructor(options?: ClientOptions);
socket: Socket | null;
connected: boolean;
options: ClientOptions;
connect(host: string, port: number, timeout?: number): Promise;
disconnect(): void;
writeBuffer(buf: Buffer): void;
sendIdentification(username: string, verificationKey?: string, unused?: number): void;
sendSetBlock(x: number, y: number, z: number, mode: number, blockType: number): void;
/** Send position using raw FShort coordinates. */
sendPosition(x: number, y: number, z: number, yaw: number, pitch: number): void;
/** Send position using block coordinates (auto-converts to FShort). */
sendPositionBlocks(bx: number, by: number, bz: number, yaw: number, pitch: number): void;
sendMessage(message: string): void;
on(event: 'connect', listener: () => void): this;
on(event: 'packet', listener: (packet: AnyPacket) => void): this;
on(event: 'level', listener: (level: LevelData) => void): this;
on(event: 'levelProgress', listener: (percent: number) => void): this;
on(event: 'ping', listener: () => void): this;
on(event: 'end', listener: () => void): this;
on(event: 'error', listener: (error: Error) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
}
export function createClient(options?: ClientOptions): ClassiCubeClient;
// ═══════════════════════════════════════════════════════════════════════════
// Server
// ═══════════════════════════════════════════════════════════════════════════
export interface ServerOptions {
port?: number;
host?: string;
/** Set false to call listen() manually (default: true when port is given) */
autoListen?: boolean;
/** Reject connections beyond this limit; 0 = unlimited (default: 0) */
maxClients?: number;
/** ms between automatic server→all pings; 0 = disabled (default: 0) */
pingInterval?: number;
}
export class ClientConnection extends EventEmitter {
constructor(socket: Socket, id: number);
socket: Socket;
id: number;
username: string | null;
state: 'login' | 'level' | 'play';
/** Arbitrary data bag for host application use. */
data: Record;
readonly remoteAddress: string;
readonly remotePort: number;
readonly isConnected: boolean;
writeBuffer(buf: Buffer): void;
sendIdentification(serverName: string, motd: string, userType?: number): void;
sendPing(): void;
sendLevelInitialize(): void;
sendLevelDataChunk(chunkData: Buffer, percentComplete: number): void;
sendLevelFinalize(xSize: number, ySize: number, zSize: number): void;
/** Full level send in one call. Compresses and chunks automatically. */
sendLevel(blocks: Buffer, xSize: number, ySize: number, zSize: number, zlibOpts?: object): Promise;
sendSetBlock(x: number, y: number, z: number, blockType: number): void;
sendSpawnPlayer(playerId: number, playerName: string, x: number, y: number, z: number, yaw?: number, pitch?: number): void;
/** Spawn at block coordinates (auto-converts to FShort). */
spawnAt(playerId: number, playerName: string, bx: number, by: number, bz: number, yaw?: number, pitch?: number): void;
sendPosition(playerId: number, x: number, y: number, z: number, yaw?: number, pitch?: number): void;
sendPositionOrientation(playerId: number, dx: number, dy: number, dz: number, yaw: number, pitch: number): void;
sendPositionUpdate(playerId: number, dx: number, dy: number, dz: number): void;
sendOrientationUpdate(playerId: number, yaw: number, pitch: number): void;
sendDespawnPlayer(playerId: number): void;
sendMessage(senderId: number, message: string): void;
/** Shorthand: sendMessage(0xFF, message). */
sendServerMessage(message: string): void;
sendUpdateUserType(userType: number): void;
/** Convenience: promote/demote this connection. */
setOperator(isOp?: boolean): void;
disconnect(reason?: string): void;
on(event: 'packet', listener: (packet: AnyPacket) => void): this;
on(event: 'end', listener: () => void): this;
on(event: 'error', listener: (error: Error) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
}
export class ClassiCubeServer extends EventEmitter {
constructor(options?: ServerOptions);
clients: Map;
readonly playerCount: number;
listen(port?: number, host?: string): Promise;
close(): Promise;
/** Disconnect all clients then close the server. */
shutdown(reason?: string): Promise;
broadcast(buf: Buffer): void;
broadcastMessage(message: string, senderId?: number): void;
broadcastExcept(excludeId: number, buf: Buffer): void;
/** Relay a player's message to everyone else. */
relayMessage(fromId: number, message: string): void;
pingAll(): void;
on(event: 'listening', listener: (info: { port: number; host: string }) => void): this;
on(event: 'connection', listener: (client: ClientConnection) => void): this;
on(event: 'disconnect', listener: (client: ClientConnection) => void): this;
on(event: 'clientError', listener: (client: ClientConnection, error: Error) => void): this;
on(event: 'error', listener: (error: Error) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
}
export function createServer(options?: ServerOptions): ClassiCubeServer;
// ═══════════════════════════════════════════════════════════════════════════
// Encoder
// ═══════════════════════════════════════════════════════════════════════════
export namespace encoder {
function writeString(buf: Buffer, offset: number, str: string): number;
function readString(buf: Buffer, offset: number): string;
function toFShort(blockPos: number): number;
function fromFShort(fshort: number): number;
function encodeServerIdentification(serverName: string, motd: string, userType?: number): Buffer;
function encodePing(): Buffer;
function encodeLevelInitialize(): Buffer;
function encodeLevelDataChunk(chunkData: Buffer, percentComplete: number): Buffer;
function encodeLevelFinalize(xSize: number, ySize: number, zSize: number): Buffer;
function encodeServerSetBlock(x: number, y: number, z: number, blockType: number): Buffer;
function encodeSpawnPlayer(playerId: number, playerName: string, x: number, y: number, z: number, yaw: number, pitch: number): Buffer;
function encodePosition(playerId: number, x: number, y: number, z: number, yaw: number, pitch: number): Buffer;
function encodePositionOrientation(playerId: number, dx: number, dy: number, dz: number, yaw: number, pitch: number): Buffer;
function encodePositionUpdate(playerId: number, dx: number, dy: number, dz: number): Buffer;
function encodeOrientationUpdate(playerId: number, yaw: number, pitch: number): Buffer;
function encodeDespawnPlayer(playerId: number): Buffer;
function encodeServerMessage(playerId: number, message: string): Buffer;
function encodeDisconnect(reason: string): Buffer;
function encodeUpdateUserType(userType: number): Buffer;
function encodeClientIdentification(username: string, verificationKey?: string, unused?: number): Buffer;
function encodeClientSetBlock(x: number, y: number, z: number, mode: number, blockType: number): Buffer;
function encodeClientPosition(x: number, y: number, z: number, yaw: number, pitch: number): Buffer;
function encodeClientMessage(message: string): Buffer;
}
// ═══════════════════════════════════════════════════════════════════════════
// Decoder
// ═══════════════════════════════════════════════════════════════════════════
export class PacketDecoder extends EventEmitter {
constructor(direction: 'client' | 'server');
direction: 'client' | 'server';
receive(data: Buffer): void;
on(event: 'packet', listener: (packet: AnyPacket) => void): this;
on(event: 'error', listener: (error: Error) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
}
// ═══════════════════════════════════════════════════════════════════════════
// Protocol module
// ═══════════════════════════════════════════════════════════════════════════
export namespace protocol {
const PROTOCOL_VERSION: number;
const STRING_LENGTH: number;
const CLIENT_PACKETS: Record;
const SERVER_PACKETS: Record;
const CLIENT_PACKET_SIZES: Record;
const SERVER_PACKET_SIZES: Record;
const BLOCK_MODE: Readonly<{ DESTROY: 0x00; CREATE: 0x01 }>;
const USER_TYPE: Readonly<{ NORMAL: 0x00; OP: 0x64 }>;
const CPE_MAGIC: 0x42;
const BLOCKS: Record;
const BLOCK_NAMES: Record;
}
// ═══════════════════════════════════════════════════════════════════════════
// Level module
// ═══════════════════════════════════════════════════════════════════════════
export interface LevelData {
blocks: Buffer;
xSize: number;
ySize: number;
zSize: number;
}
export namespace level {
const CHUNK_SIZE: number;
function gzip(buf: Buffer, options?: object): Promise;
function gunzip(buf: Buffer): Promise;
function compressLevel(blocks: Buffer, opts?: object): Promise;
function chunkLevel(gzipped: Buffer): { chunk: Buffer; percent: number }[];
function prepareLevel(blocks: Buffer, opts?: object): Promise<{ chunk: Buffer; percent: number }[]>;
function blockIndex(x: number, z: number, y: number, xSize: number, zSize: number): number;
function buildMap(xSize: number, ySize: number, zSize: number, fn: (x: number, y: number, z: number) => number): Buffer;
function buildFlatMap(xSize: number, ySize: number, zSize: number, groundY?: number): Buffer;
function buildSphereMap(xSize: number, ySize: number, zSize: number, radius?: number, shell?: number, blockType?: number): Buffer;
function buildCheckerMap(xSize: number, ySize: number, zSize: number): Buffer;
class LevelAssembler {
reset(): void;
push(data: Buffer, length: number): void;
decompress(): Promise;
readonly byteLength: number;
}
}
// ═══════════════════════════════════════════════════════════════════════════
// UUID helpers
// ═══════════════════════════════════════════════════════════════════════════
export namespace jugadorUUID {
function generarUUID(username: string): string;
function validarUUID(uuid: string): boolean;
function uuidToHex(uuid: string): string;
function hexToUUID(hex: string): string;
}
// ═══════════════════════════════════════════════════════════════════════════
// Packet union types
// ═══════════════════════════════════════════════════════════════════════════
export type AnyPacket =
| IdentificationPacket
| PingPacket
| LevelInitializePacket
| LevelDataChunkPacket
| LevelFinalizePacket
| SetBlockPacket
| SpawnPlayerPacket
| PositionPacket
| PositionOrientationPacket
| PositionUpdatePacket
| OrientationUpdatePacket
| DespawnPlayerPacket
| MessagePacket
| DisconnectPacket
| UpdateUserTypePacket
| UnknownPacket;
export interface IdentificationPacket { name: 'identification'; id: number; protocolVersion: number; username?: string; verificationKey?: string; serverName?: string; motd?: string; userType?: number; unused?: number; }
export interface PingPacket { name: 'ping'; id: number; }
export interface LevelInitializePacket { name: 'levelInitialize'; id: number; }
export interface LevelDataChunkPacket { name: 'levelDataChunk'; id: number; chunkLength: number; chunkData: Buffer; percentComplete: number; }
export interface LevelFinalizePacket { name: 'levelFinalize'; id: number; xSize: number; ySize: number; zSize: number; }
export interface SetBlockPacket { name: 'setBlock'; id: number; x: number; y: number; z: number; blockType: number; mode?: number; }
export interface SpawnPlayerPacket { name: 'spawnPlayer'; id: number; playerId: number; playerName: string; x: number; y: number; z: number; yaw: number; pitch: number; }
export interface PositionPacket { name: 'position'; id: number; playerId: number; x: number; y: number; z: number; yaw: number; pitch: number; }
export interface PositionOrientationPacket { name: 'positionOrientation'; id: number; playerId: number; dx: number; dy: number; dz: number; yaw: number; pitch: number; }
export interface PositionUpdatePacket { name: 'positionUpdate'; id: number; playerId: number; dx: number; dy: number; dz: number; }
export interface OrientationUpdatePacket{ name: 'orientationUpdate'; id: number; playerId: number; yaw: number; pitch: number; }
export interface DespawnPlayerPacket { name: 'despawnPlayer'; id: number; playerId: number; }
export interface MessagePacket { name: 'message'; id: number; playerId?: number; message: string; unused?: number; }
export interface DisconnectPacket { name: 'disconnect'; id: number; reason: string; }
export interface UpdateUserTypePacket { name: 'updateUserType'; id: number; userType: number; }
export interface UnknownPacket { name: 'unknown'; id: number; raw: Buffer; }
// ═══════════════════════════════════════════════════════════════════════════
// Auth
// ═══════════════════════════════════════════════════════════════════════════
// ── Static helpers ────────────────────────────────────────────────────────
/**
* Generate a cryptographically random salt string.
* @param length Number of characters (default 16).
*/
export function generateSalt(length?: number): string;
/**
* Compute the MPPass a player should send: lowercase MD5 hex of (salt + username).
*/
export function computeMPPass(salt: string, username: string): string;
/**
* Verify a player's MPPass. Case-insensitive, matching original ClassiCube behaviour.
*/
export function verifyMPPass(salt: string, username: string, mppass: string): boolean;
// ── ClassiCubeAuth ────────────────────────────────────────────────────────
export interface ClassiCubeAuthOptions {
/** Server name shown in the classicube.net server list. */
name: string;
/** TCP port the server listens on. */
port: number;
/** Maximum players (default 20). */
maxPlayers?: number;
/** Show in the public server list (default true). */
public?: boolean;
/** Server software name (optional). */
software?: string;
/** Set true if the server is accessible via the ClassiCube web client. */
web?: boolean;
/** Provide your own salt; otherwise one is auto-generated. */
salt?: string;
}
export interface HeartbeatResult {
/** The classicube.net play URL for this server. */
url: string;
/** Non-null if the heartbeat request failed. */
error: string | null;
}
/**
* Server-side auth: MPPass verification + classicube.net heartbeat.
*
* @example
* const auth = new ClassiCubeAuth({ name: 'My Server', port: 25565 });
* const url = await auth.startHeartbeat(() => server.playerCount);
* // on player connect:
* if (!auth.verify(username, mppass)) client.disconnect('Bad login');
*/
export class ClassiCubeAuth {
constructor(options: ClassiCubeAuthOptions);
/** The random salt used for MPPass verification. Include in heartbeats. */
readonly salt: string;
/** The last play URL returned by classicube.net, or null before first heartbeat. */
readonly serverUrl: string | null;
/** Verify a player's MPPass against this server's salt. */
verify(username: string, mppass: string): boolean;
/** Compute the MPPass you expect from a specific player (for debugging / invites). */
computeExpected(username: string): string;
/** Send a single heartbeat. Returns the server URL and any error. */
sendHeartbeat(playerCount?: number): Promise;
/**
* Start periodic heartbeats (every 45 s).
* Resolves with the play URL after the first successful heartbeat.
* @param getPlayerCount Called each interval to fetch the current player count.
*/
startHeartbeat(getPlayerCount?: () => number): Promise;
/** Stop the periodic heartbeat. */
stopHeartbeat(): void;
}
// ── ClassiCubeAccount ─────────────────────────────────────────────────────
export interface ClassiCubeServerInfo {
/** Short hash ID (used in play URLs and API calls). */
hash: string;
/** Display name. */
name: string;
/** IP address to connect to. */
ip: string;
/** TCP port. */
port: number;
/**
* Your MPPass for this server.
* Pass as `verificationKey` in `sendIdentification()`.
*/
mppass: string;
/** Server software name. */
software: string;
/** Current player count. */
players: number;
/** Maximum player count. */
maxPlayers: number;
/** Whether the server is currently online. */
online: boolean;
/** Full classicube.net play URL. */
playUrl: string;
}
/**
* Client/Bot-side: log into a real ClassiCube account and obtain MPPasses.
*
* @example
* const acct = new ClassiCubeAccount();
* await acct.login('BotName', 'mypassword');
* const server = await acct.getServer('abc123hash');
* await client.connect(server.ip, server.port);
* client.sendIdentification(acct.username, server.mppass);
*/
export class ClassiCubeAccount {
constructor();
/** Username after successful login, null before. */
username: string | null;
/** True after a successful login() call. */
loggedIn: boolean;
/**
* Authenticate with classicube.net (two-step CSRF login).
* @throws If credentials are wrong or network fails.
*/
login(username: string, password: string): Promise;
/** Fetch the full server list including your MPPass for each entry. */
getServers(): Promise;
/**
* Fetch info + MPPass for one specific server by its hash.
* @param hash The short hash in the play URL (e.g. 'abc123').
*/
getServer(hash: string): Promise;
/**
* Find the first server whose name contains the query (case-insensitive).
* @throws If no matching server is found.
*/
findServer(nameQuery: string): Promise;
}
// ── auth namespace ────────────────────────────────────────────────────────
export namespace auth {
export { ClassiCubeAuth, ClassiCubeAccount, generateSalt, computeMPPass, verifyMPPass };
export const HEARTBEAT_URL: string;
export const HEARTBEAT_INTERVAL: number;
}