initital commit

This commit is contained in:
Lee 2024-04-14 21:11:46 +01:00
parent 5876f13f70
commit eb0facd38e
25 changed files with 3120 additions and 0 deletions

11
jest.config.ts Normal file
View File

@ -0,0 +1,11 @@
import { Config } from "jest";
// Sync object
const config: Config = {
verbose: true,
testMatch: ["**/test/**/*.ts"],
transform: {
"^.+\\.tsx?$": "ts-jest",
},
};
export default config;

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "mcutils-library",
"version": "1.0.1",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"scripts": {
"build": "tsc",
"dev": "ts-node src/index.ts",
"test": "jest"
},
"license": "MIT",
"dependencies": {
"axios": "^1.6.8",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7"
}
}

2434
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

18
src/common/WebRequest.ts Normal file
View File

@ -0,0 +1,18 @@
import axios, { AxiosResponse } from "axios";
export default class WebRequest {
/**
* Gets a response from the given URL.
*
* @param url the url
* @returns the response
*/
public static get(url: string): Promise<AxiosResponse<any, any>> {
return axios.get(url, {
validateStatus: () => true, // Don't throw errors
headers: {
"User-Agent": "McUtils-JS-Library/1.0",
},
});
}
}

24
src/index.ts Normal file
View File

@ -0,0 +1,24 @@
import MojangTools from "./server/mojang";
import PlayerTools from "./server/player";
import ServerTools from "./server/server";
export class MinecraftUtils {
public static API_ENDPOINT = "https://api.mcutils.xyz";
/**
* The server instance.
*/
public server = new ServerTools();
/**
* The player instance.
*/
public player = new PlayerTools();
/**
* The Mojang instance.
*/
public mojang = new MojangTools();
}
export default new MinecraftUtils();

25
src/server/mojang.ts Normal file
View File

@ -0,0 +1,25 @@
import { MinecraftUtils } from "..";
import WebRequest from "../common/WebRequest";
import { CachedEndpointStatus } from "../types/cache/cachedEndpointStatus";
export default class MojangTools {
public endpointStatusEndpoint = MinecraftUtils.API_ENDPOINT + "/mojang/status";
/**
* Gets the Mojang API status.
*
* @returns the Mojang API status
*/
public getMojangEndpointStatus(): Promise<CachedEndpointStatus> {
return new Promise(async (resolve, reject) => {
const response = await WebRequest.get(this.endpointStatusEndpoint);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(data);
});
}
}

74
src/server/player.ts Normal file
View File

@ -0,0 +1,74 @@
import { MinecraftUtils } from "..";
import WebRequest from "../common/WebRequest";
import { CachedPlayer } from "../types/cache/cachedPlayer";
import { CachedUsernameToUuid } from "../types/cache/cachedUsernameToUuid";
export default class PlayerTools {
public playerEndpoint = MinecraftUtils.API_ENDPOINT + "/player/{id}";
public playerUsernameToUuidEndpoint = MinecraftUtils.API_ENDPOINT + "/player/uuid/{id}";
public playerSkinPartEndpoint = MinecraftUtils.API_ENDPOINT + "/player/{part}/{id}";
/**
* Gets information about a Minecraft player.
*
* @param id the id of the player
* @returns the player information, or null if the player does not exist
*/
public getPlayer(id: string): Promise<CachedPlayer> {
return new Promise(async (resolve, reject) => {
const url = this.playerEndpoint.replace("{id}", id);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(data);
});
}
/**
* Gets the UUID of a Minecraft player.
*
* @param id the id of the player
* @returns the player's UUID, or null if the player does not exist
*/
public getPlayerUuid(id: string): Promise<CachedUsernameToUuid> {
return new Promise(async (resolve, reject) => {
const url = this.playerUsernameToUuidEndpoint.replace("{id}", id);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(data);
});
}
/**
* Gets a part of a Minecraft player's skin.
*
* @param part the part of the skin
* @param id the id of the player
* @returns the player's skin part, or null if the player does not exist
*/
public getPlayerSkinPart(part: string, id: string): Promise<Buffer> {
return new Promise(async (resolve, reject) => {
const url = this.playerSkinPartEndpoint.replace("{part}", part).replace("{id}", id);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(Buffer.from(data, "utf-8"));
});
}
}

78
src/server/server.ts Normal file
View File

@ -0,0 +1,78 @@
import { MinecraftUtils } from "..";
import WebRequest from "../common/WebRequest";
import { CachedMinecraftServer } from "../types/cache/cachedMinecraftServer";
import { ServerPlatform } from "../types/server/platform";
export default class ServerTools {
public serverEndpoint = MinecraftUtils.API_ENDPOINT + "/server/{platform}/{hostname}";
public serverIconEndpoint = MinecraftUtils.API_ENDPOINT + "/server/icon/{hostname}";
public blockedServerEndpoint = MinecraftUtils.API_ENDPOINT + "/server/blocked/{hostname}";
/**
* Gets information about a Minecraft server.
*
* @param platform the platform of server
* @param hostname the hostname of the server
* @param port the port of the server
* @returns the server information, or null if the server does not exist
*/
public getServer(platform: ServerPlatform, hostname: string, port?: 25565): Promise<CachedMinecraftServer> {
return new Promise(async (resolve, reject) => {
const ip = port ? `${hostname}:${port}` : hostname;
const url = this.serverEndpoint.replace("{platform}", platform).replace("{hostname}", ip);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(data);
});
}
/**
* Gets the icon of a Java Minecraft server.
*
* @param hostname the hostname of the server
* @param port the port of the server
* @returns the server icon, or null if the server does not have an icon
*/
public getServerIcon(hostname: string, port?: 25565): Promise<Buffer> {
return new Promise(async (resolve, reject) => {
const ip = port ? `${hostname}:${port}` : hostname;
const url = this.serverIconEndpoint.replace("{hostname}", ip);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(Buffer.from(data, "utf-8"));
});
}
/**
* Gets the Mojang blocked status of a Minecraft server.
*
* @param hostname the hostname of the server
* @returns true if the server is blocked, false otherwise
*/
public getBlockedStatus(hostname: string): Promise<boolean> {
return new Promise(async (resolve, reject) => {
const url = this.blockedServerEndpoint.replace("{hostname}", hostname);
const response = await WebRequest.get(url);
const data = response.data;
if (response.status !== 200) {
reject(null);
return;
}
resolve(data.blocked);
});
}
}

12
src/types/cache/cache.ts vendored Normal file
View File

@ -0,0 +1,12 @@
export type Cache = {
/**
* Whether the request was in the cache.
*/
cached: boolean;
/**
* The time the request was cached.
* This will be -1 if the request was not cached.
*/
cachedTime: number;
};

View File

@ -0,0 +1,8 @@
import { EndpointStatus } from "../mojang/endpointStatus";
export interface CachedEndpointStatus extends Cache {
/**
* The cached endpoint status.
*/
status: EndpointStatus;
}

View File

@ -0,0 +1,10 @@
import BedrockMinecraftServer from "../server/bedrockServer";
import JavaMinecraftServer from "../server/javaServer";
import { Cache } from "./cache";
export interface CachedMinecraftServer extends Cache {
/**
* The cached server.
*/
server: JavaMinecraftServer | BedrockMinecraftServer;
}

8
src/types/cache/cachedPlayer.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { Player } from "../player/player";
export interface CachedPlayer extends Cache {
/**
* The cached player.
*/
player: Player;
}

View File

@ -0,0 +1,3 @@
import { UsernameToUuid } from "../player/usernameToUuid";
export interface CachedUsernameToUuid extends Cache, UsernameToUuid {}

21
src/types/error.ts Normal file
View File

@ -0,0 +1,21 @@
export type Error = {
/**
* The status of the error.
*/
status: string;
/**
* The HTTP status code of the error.
*/
code: number;
/**
* The message of the error.
*/
message: string;
/**
* The timestamp of the error.
*/
timestamp: string;
};

View File

@ -0,0 +1,24 @@
export type EndpointStatus = {
/**
* The status of the endpoints.
*/
endpoints: Map<string, Status>;
};
enum Status {
/**
* The service is online and operational.
*/
ONLINE,
/**
* The service is online, but may be experiencing issues.
* This could be due to high load or other issues.
*/
DEGRADED,
/**
* The service is offline and not operational.
*/
OFFLINE,
}

View File

@ -0,0 +1,61 @@
export type Player = {
/**
* The player's unique id.
*/
uniqueId: string;
/**
* The player's trimmed unique id.
*/
trimmedUniqueId: string;
/**
* The player's username.
*/
username: string;
/**
* The player's skin.
*/
skin: Skin;
/**
* The player's cape.
*/
cape: Cape;
};
type Skin = {
/**
* The url of the skin.
*/
url: string;
/**
* The model of the skin.
*/
model: Model;
/**
* Whether this skin uses the
* legacy skin format.
*/
legacy: boolean;
/**
* The parts of the skin.
*/
parts: Record<string, string>;
};
enum Model {
DEFAULT,
SLIM,
}
type Cape = {
/**
* The url of the cape.
*/
url: string;
};

View File

@ -0,0 +1,11 @@
export type UsernameToUuid = {
/**
* The username of the player.
*/
username: string;
/**
* The UUID of the player.
*/
uuid: string;
};

View File

@ -0,0 +1,54 @@
import { MinecraftServer } from "./server";
export default interface BedrockMinecraftServer extends MinecraftServer {
/**
* The edition of the server.
*/
edition: Edition;
/**
* The version of the server.
*/
version: Version;
/**
* The gamemode of the server.
*/
gamemode: Gamemode;
}
enum Edition {
/**
* Minecraft: Pocket Edition.
*/
MCPE,
/**
* Minecraft: Education Edition.
*/
MCEE,
}
type Version = {
/**
* The protocol version of the server.
*/
protocol: number;
/**
* The version name of the server.
*/
name: string;
};
type Gamemode = {
/**
* The name of the gamemode.
*/
name: string;
/**
* The numeric ID of the gamemode.
*/
numericId: number;
};

View File

@ -0,0 +1,67 @@
import { MinecraftServer } from "./server";
export default interface JavaMinecraftServer extends MinecraftServer {
/**
* The version of the server.
*/
version: Version;
/**
* The favicon of the server.
*/
favicon: Favicon;
/**
* Whether the server prevents chat reports.
*/
preventsChatReports: boolean;
/**
* Whether the server enforces secure chat.
*/
enforcesSecureChat: boolean;
/**
* Whether the server has previews chat enabled.
*/
previewsChat: boolean;
/**
* The mojang blocked status for the server.
*/
mojangBlocked: boolean;
}
type Favicon = {
/**
* The base64 encoded favicon.
*/
base64: string;
/**
* The URL to the favicon.
*/
url: string;
};
type Version = {
/**
* The version name of the server.
*/
name: string;
/**
* The server's platform.
*/
platform: string;
/**
* The protocol version of the server.
*/
protocol: number;
/**
* The protocol name of the server.
*/
protocolName: string;
};

View File

@ -0,0 +1,4 @@
export enum ServerPlatform {
Java = "java",
Bedrock = "bedrock",
}

View File

@ -0,0 +1,77 @@
export type MinecraftServer = {
/**
* The hostname of the server.
*/
hostname: string;
/**
* The ip of the server.
*/
ip: string;
/**
* The port of the server.
*/
port: number;
/**
* The DNS records for the server.
*/
records: [];
/**
* The MOTD of the server.
*/
motd: Motd;
/**
* The players on the server.
*/
players: Players;
};
type Motd = {
/**
* The raw MOTD.
*/
raw: string;
/**
* The clean MOTD, without any colors.
*/
clean: string;
/**
* The HTML MOTD, with HTML tags for colors.
*/
html: string;
};
type Players = {
/**
* The max players allowed on the server.
*/
max: number;
/**
* The online players on the server.
*/
online: number;
/**
* The list of players on the server.
*/
sample: PlayerSample[];
};
type PlayerSample = {
/**
* The UUID of the player.
*/
id: string;
/**
* The name of the player.
*/
name: string;
};

7
test/mojang.ts Normal file
View File

@ -0,0 +1,7 @@
import mcUtils from "../src/index";
test("ensureMojangEndpointStatusLookupSuccess", async () => {
const response = await mcUtils.mojang.getMojangEndpointStatus();
expect(response).toHaveProperty("endpoints");
});

32
test/player.ts Normal file
View File

@ -0,0 +1,32 @@
import mcUtils from "../src/index";
test("ensureGetPlayerLookupSuccess", async () => {
const response = await mcUtils.player.getPlayer("Notch");
const { player } = response;
expect(player).toBeDefined();
expect(player).toHaveProperty("username");
});
test("ensureGetPlayerUuidSuccess", async () => {
const player = await mcUtils.player.getPlayerUuid("Notch");
expect(player).toBeDefined();
expect(player).toHaveProperty("username");
expect(player).toHaveProperty("uniqueId");
});
test("ensureGetPlayerSkinPartSuccess", async () => {
const response = await mcUtils.player.getPlayer("Notch");
const { player } = response;
const skin = player.skin;
const skinParts = skin.parts;
// Test each skin part
for (const part in skinParts) {
const partBuffer = await mcUtils.player.getPlayerSkinPart(part, player.uniqueId);
expect(partBuffer).toBeDefined();
expect(partBuffer.byteLength).toBeGreaterThan(0);
}
});

21
test/server.ts Normal file
View File

@ -0,0 +1,21 @@
import mcUtils from "../src/index";
import { ServerPlatform } from "../src/types/server/platform";
test("ensureGetServerLookupSuccess", async () => {
const cachedServer = await mcUtils.server.getServer(ServerPlatform.Java, "mc.hypixel.net");
const { server } = cachedServer;
expect(server).toBeDefined();
expect(server).toHaveProperty("hostname"); // The server was found
});
test("ensureGetServerIconSuccess", async () => {
const icon = await mcUtils.server.getServerIcon("mc.hypixel.net");
expect(icon).toBeDefined();
expect(icon.byteLength).toBeGreaterThan(0); // The server has an icon
});
test("ensureGetServerBlockedStatusSuccess", async () => {
const blockedStatus = await mcUtils.server.getBlockedStatus("mc.hypixel.net");
expect(blockedStatus).toBe(false); // The server is not blocked
});

9
tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"declaration": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}