minecraft-toolkit 0.1.0 → 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 26bz https://26bz.com/
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # minecraft-toolkit
2
+
3
+ <!-- automd:badges name="minecraft-toolkit" github="26bz/minecraft-toolkit" license -->
4
+
5
+ [![npm version](https://img.shields.io/npm/v/minecraft-toolkit)](https://npmjs.com/package/minecraft-toolkit)
6
+ [![npm downloads](https://img.shields.io/npm/dm/minecraft-toolkit)](https://npm.chart.dev/minecraft-toolkit)
7
+ [![license](https://img.shields.io/github/license/26bz/minecraft-toolkit)](https://github.com/26bz/minecraft-toolkit/blob/main/LICENSE)
8
+
9
+ <!-- /automd -->
10
+
11
+ Lightweight Mojang player utilities (profile, skin, UUID) for Node, Vite, and edge projects.
12
+
13
+ > This toolkit wraps Mojang APIs. Rate limits and availability still apply. Write endpoints (name change, skin upload) are not yet included.
14
+
15
+ ## Installation
16
+
17
+ <!-- automd:pm-install name="minecraft-toolkit" -->
18
+
19
+ ```sh
20
+ # ✨ Auto-detect
21
+ npx nypm install minecraft-toolkit
22
+
23
+ # npm
24
+ npm install minecraft-toolkit
25
+
26
+ # yarn
27
+ yarn add minecraft-toolkit
28
+
29
+ # pnpm
30
+ pnpm install minecraft-toolkit
31
+
32
+ # bun
33
+ bun install minecraft-toolkit
34
+
35
+ # deno
36
+ deno install minecraft-toolkit
37
+ ```
38
+
39
+ <!-- /automd -->
40
+
41
+ ## Core Helpers
42
+
43
+ ```ts
44
+ import {
45
+ fetchPlayerProfile,
46
+ fetchPlayerSkin,
47
+ fetchPlayerUUID,
48
+ fetchPlayerSummary,
49
+ fetchNameHistory,
50
+ fetchPlayers,
51
+ resolvePlayer,
52
+ fetchSkinMetadata,
53
+ } from "minecraft-toolkit";
54
+
55
+ const profile = await fetchPlayerProfile("26bz");
56
+ const summary = await fetchPlayerSummary("26bz");
57
+ const skin = await fetchPlayerSkin("26bz");
58
+ const uuid = await fetchPlayerUUID("26bz");
59
+ const history = await fetchNameHistory("069a79f444e94726a5befca90e38aaf5");
60
+ const batch = await fetchPlayers(["Notch", "26bz"], { delayMs: 50 });
61
+ const resolved = await resolvePlayer("069a79f444e94726a5befca90e38aaf5");
62
+ const skinMeta = await fetchSkinMetadata("26bz");
63
+ ```
64
+
65
+ Helpers are HTTP-agnostic and run anywhere `fetch` exists (Node 18+, Bun, Workers). All errors surface as `MinecraftToolkitError`.
66
+
67
+ ## Texture & Identity Utilities
68
+
69
+ ```ts
70
+ import {
71
+ isValidUsername,
72
+ isUUID,
73
+ normalizeUUID,
74
+ uuidWithDashes,
75
+ uuidWithoutDashes,
76
+ getSkinURL,
77
+ getCapeURL,
78
+ getSkinModel,
79
+ extractTextureHash,
80
+ } from "minecraft-toolkit";
81
+
82
+ isValidUsername("26bz"); // true
83
+ uuidWithDashes("069a79f444e94726a5befca90e38aaf5");
84
+ const skinUrl = getSkinURL(await fetchPlayerProfile("26bz"));
85
+ const hash = extractTextureHash(skinUrl);
86
+ const model = getSkinModel(skinUrl); // "slim" | "classic"
87
+ ```
88
+
89
+ ## Skin Metadata & Color Sampling
90
+
91
+ ```ts
92
+ import { fetchSkinMetadata, computeSkinDominantColor } from "minecraft-toolkit";
93
+
94
+ const meta = await fetchSkinMetadata("26bz", {
95
+ dominantColor: true,
96
+ sampleRegion: { x: 8, y: 8, width: 8, height: 8 },
97
+ });
98
+
99
+ console.log(meta.dominantColor); // e.g. "#f2d2a9"
100
+
101
+ const accent = await computeSkinDominantColor(meta.skin.url, {
102
+ x: 40,
103
+ y: 8,
104
+ width: 8,
105
+ height: 8,
106
+ });
107
+ ```
108
+
109
+ ## Account Helpers
110
+
111
+ A valid Microsoft/Xbox Live access token is required for `minecraftservices.com` endpoints. Missing or expired tokens throw `MinecraftToolkitError` with `statusCode: 401`.
112
+
113
+ ```ts
114
+ import {
115
+ fetchNameChangeInfo,
116
+ checkNameAvailability,
117
+ validateGiftCode,
118
+ fetchBlockedServers,
119
+ } from "minecraft-toolkit";
120
+
121
+ const accessToken = process.env.MC_ACCESS_TOKEN;
122
+
123
+ const windowInfo = await fetchNameChangeInfo(accessToken);
124
+ const availability = await checkNameAvailability("fresh_name", accessToken);
125
+ const isGiftValid = await validateGiftCode("ABCD-1234", accessToken);
126
+ const blockedServer = await fetchBlockedServers(); // no token required
127
+ ```
128
+
129
+ `validateGiftCode` returns `true`/`false` for 200/404 responses without throwing.
130
+
131
+ ## Server Status Helpers
132
+
133
+ Probe Java and Bedrock servers without bringing your own RakNet/TCP logic.
134
+
135
+ ```ts
136
+ import {
137
+ fetchServerStatus,
138
+ fetchJavaServerStatus,
139
+ fetchBedrockServerStatus,
140
+ } from "minecraft-toolkit";
141
+
142
+ const javaStatus = await fetchJavaServerStatus({ host: "mc.hypixel.net", port: 25565 });
143
+ const bedrockStatus = await fetchBedrockServerStatus({ host: "play.example.net", port: 19132 });
144
+
145
+ // fetchServerStatus picks the right probe based on the `edition` field
146
+ const autoStatus = await fetchServerStatus({ host: "my.realm.net", edition: "bedrock" });
147
+
148
+ console.log(javaStatus.players.online, bedrockStatus.motd.text);
149
+ ```
150
+
151
+ Both helpers normalize MOTD text, favicon/Base64 icons, latency, and version info. Errors surface as
152
+ `MinecraftToolkitError` with contextual status codes.
153
+
154
+ ## Minecraft Formatting Renderer
155
+
156
+ Convert legacy `§` or `&` codes into safe HTML fragments or CSS class spans.
157
+
158
+ ```ts
159
+ import { toHTML, generateCSS, stripCodes, hasCodes, convertPrefix } from "minecraft-toolkit";
160
+
161
+ const motd = "§aWelcome §lHeroes§r!";
162
+
163
+ const inline = toHTML(motd); // <span style="color: #55ff55">Welcome ...</span>
164
+
165
+ const classes = toHTML(motd, { mode: "class", classPrefix: "mc" });
166
+ const css = generateCSS(); // drop into a <style> tag
167
+
168
+ stripCodes(motd); // "Welcome Heroes!"
169
+ hasCodes(motd); // true
170
+ convertPrefix("&aHi", "toSection"); // "§aHi"
171
+ ```
172
+
173
+ `getMaps()` exposes the color and format metadata if you want to build custom renderers.
174
+
175
+ ## License
176
+
177
+ Published under the [MIT](https://github.com/26bz/minecraft-toolkit/blob/main/LICENSE) license.
178
+ Made by [26bz](https://github.com/26bz)
179
+ <br><br>
180
+ <a href="https://github.com/26bz/minecraft-toolkit/graphs/contributors">
181
+ <img src="https://contrib.rocks/image?repo=26bz/minecraft-toolkit" />
182
+ </a>
package/index.js ADDED
@@ -0,0 +1,35 @@
1
+ export {
2
+ fetchPlayerProfile,
3
+ fetchPlayerSkin,
4
+ fetchPlayerUUID,
5
+ fetchUsernameByUUID,
6
+ fetchNameHistory,
7
+ fetchPlayers,
8
+ fetchPlayerSummary,
9
+ playerExists,
10
+ hasSkinChanged,
11
+ } from "./src/player/profile/index.js";
12
+ export { fetchSkinMetadata, computeSkinDominantColor } from "./src/player/skin.js";
13
+ export { isValidUsername } from "./src/player/identity/index.js";
14
+ export { getSkinURL, getCapeURL, getSkinModel, extractTextureHash } from "./src/player/textures.js";
15
+ export { resolvePlayer } from "./src/player/resolve.js";
16
+ export {
17
+ fetchNameChangeInfo,
18
+ checkNameAvailability,
19
+ validateGiftCode,
20
+ fetchBlockedServers,
21
+ } from "./src/player/account/index.js";
22
+ export { createPlayerApp, createPlayerHandlers } from "./src/h3/routes.js";
23
+ export {
24
+ fetchServerStatus,
25
+ fetchJavaServerStatus,
26
+ fetchBedrockServerStatus,
27
+ } from "./src/server/status.js";
28
+ export {
29
+ toHTML,
30
+ stripCodes,
31
+ generateCSS,
32
+ hasCodes,
33
+ convertPrefix,
34
+ getMaps,
35
+ } from "./src/utils/formatting.js";
package/package.json CHANGED
@@ -1,12 +1,76 @@
1
1
  {
2
2
  "name": "minecraft-toolkit",
3
- "version": "0.1.0",
4
- "description": "",
3
+ "version": "0.1.2",
4
+ "description": "Developer toolkit for working with Mojang Minecraft player data, skins, and utilities.",
5
+ "keywords": [
6
+ "minecraft",
7
+ "minecraft-api",
8
+ "minecraft-player",
9
+ "minecraft-skins",
10
+ "minecraft-tools",
11
+ "minecraft-utils",
12
+ "minecraft-uuid",
13
+ "mojang",
14
+ "mojang-api"
15
+ ],
16
+ "homepage": "https://github.com/26bz/minecraft-toolkit",
17
+ "bugs": {
18
+ "url": "https://github.com/26bz/minecraft-toolkit/issues"
19
+ },
5
20
  "license": "MIT",
6
- "author": "",
7
- "type": "commonjs",
8
- "main": "index.js",
21
+ "author": {
22
+ "name": "26bz",
23
+ "email": "26bz@proton.me",
24
+ "url": "https://26bz.com"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/26bz/minecraft-toolkit"
29
+ },
30
+ "files": [
31
+ "src",
32
+ "index.js",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "type": "module",
37
+ "sideEffects": false,
38
+ "types": "./index.d.ts",
39
+ "exports": {
40
+ ".": "./index.js",
41
+ "./player": "./src/player/profile/index.js",
42
+ "./skin": "./src/player/skin.js",
43
+ "./identity": "./src/player/identity/index.js",
44
+ "./textures": "./src/player/textures.js"
45
+ },
9
46
  "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- }
47
+ "test": "vitest",
48
+ "test:watch": "vitest watch",
49
+ "test:coverage": "vitest run --coverage",
50
+ "lint": "oxlint",
51
+ "lint:fix": "oxlint --fix",
52
+ "fmt": "oxfmt",
53
+ "fmt:check": "oxfmt --check",
54
+ "prepare": "husky install",
55
+ "docs:sync": "automd"
56
+ },
57
+ "dependencies": {
58
+ "h3": "2.0.1-rc.14",
59
+ "pngjs": "^7.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "automd": "^0.3.2",
63
+ "husky": "^9.1.7",
64
+ "lint-staged": "^16.3.2",
65
+ "oxfmt": "^0.36.0",
66
+ "oxlint": "^1.51.0",
67
+ "vitest": "^4.0.18"
68
+ },
69
+ "lint-staged": {
70
+ "*.{js,jsx,ts,tsx,mjs,cjs}": "pnpm run lint"
71
+ },
72
+ "engines": {
73
+ "node": ">=18"
74
+ },
75
+ "packageManager": "pnpm@10.30.2"
12
76
  }
@@ -0,0 +1,30 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const pkg = require("../package.json");
5
+
6
+ export const PACKAGE_METADATA = {
7
+ name: pkg.name,
8
+ version: pkg.version,
9
+ description: pkg.description,
10
+ };
11
+
12
+ export const DEFAULT_JAVA_PORT = 25565;
13
+ export const DEFAULT_BEDROCK_PORT = 19132;
14
+ export const DEFAULT_CACHE_TTL_SECONDS = 30;
15
+ export const DEFAULT_TIMEOUT_MS = 5000;
16
+ export const DEFAULT_PROTOCOL_VERSION = 760; // Minecraft 1.20.4
17
+
18
+ export const MOJANG_PROFILE_BASE = "https://api.mojang.com/users/profiles/minecraft";
19
+ export const SESSION_PROFILE_BASE = "https://sessionserver.mojang.com/session/minecraft/profile";
20
+ export const NAME_HISTORY_BASE = "https://api.mojang.com/user/profiles";
21
+
22
+ export const USER_AGENT = `${pkg.name}/${pkg.version}`;
23
+ export const DEFAULT_HEADERS = {
24
+ accept: "application/json",
25
+ "user-agent": USER_AGENT,
26
+ };
27
+
28
+ export const RAKNET_MAGIC = Buffer.from([
29
+ 0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78,
30
+ ]);
package/src/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ export class MinecraftToolkitError extends Error {
2
+ constructor(message, { statusCode = 500, cause } = {}) {
3
+ super(message);
4
+ this.name = "MinecraftToolkitError";
5
+ this.statusCode = statusCode;
6
+ this.cause = cause;
7
+ }
8
+ }
@@ -0,0 +1,218 @@
1
+ import { H3, defineHandler, definePlugin, readBody, getQuery } from "h3";
2
+ import { MinecraftToolkitError } from "../errors.js";
3
+ import {
4
+ fetchPlayerProfile,
5
+ fetchPlayerSkin,
6
+ fetchPlayerSummary,
7
+ fetchPlayerUUID,
8
+ fetchPlayers,
9
+ fetchNameHistory,
10
+ playerExists,
11
+ } from "../player/profile/index.js";
12
+ import { resolvePlayer } from "../player/resolve.js";
13
+ import {
14
+ fetchNameChangeInfo,
15
+ checkNameAvailability,
16
+ validateGiftCode,
17
+ fetchBlockedServers,
18
+ } from "../player/account/index.js";
19
+ import { fetchServerStatus } from "../server/status.js";
20
+ import { normalizeAddress, validatePort } from "../utils/validation.js";
21
+
22
+ function requireParam(event, key, message) {
23
+ const value = event.context.params?.[key];
24
+ if (!value) {
25
+ throw new MinecraftToolkitError(message, { statusCode: 400 });
26
+ }
27
+ return value;
28
+ }
29
+
30
+ export function createPlayerHandlers() {
31
+ const profileHandler = defineHandler(async (event) => {
32
+ const username = requireParam(event, "username", "Username parameter is required");
33
+ return fetchPlayerProfile(username);
34
+ });
35
+
36
+ const skinHandler = defineHandler(async (event) => {
37
+ const username = requireParam(event, "username", "Username parameter is required");
38
+ return fetchPlayerSkin(username);
39
+ });
40
+
41
+ const summaryHandler = defineHandler(async (event) => {
42
+ const username = requireParam(event, "username", "Username parameter is required");
43
+ return fetchPlayerSummary(username);
44
+ });
45
+
46
+ const uuidHandler = defineHandler(async (event) => {
47
+ const username = requireParam(event, "username", "Username parameter is required");
48
+ return fetchPlayerUUID(username);
49
+ });
50
+
51
+ const resolverHandler = defineHandler(async (event) => {
52
+ const input = requireParam(event, "input", "Username or UUID parameter is required");
53
+ return resolvePlayer(input);
54
+ });
55
+
56
+ const nameHistoryHandler = defineHandler(async (event) => {
57
+ const uuid = requireParam(event, "uuid", "UUID parameter is required");
58
+ return fetchNameHistory(uuid);
59
+ });
60
+
61
+ const existsHandler = defineHandler(async (event) => {
62
+ const username = requireParam(event, "username", "Username parameter is required");
63
+ const exists = await playerExists(username);
64
+ return { username, exists };
65
+ });
66
+
67
+ const batchHandler = defineHandler(async (event) => {
68
+ const body = (await readBody(event)) ?? {};
69
+ const usernames = Array.isArray(body.usernames) ? body.usernames : null;
70
+ if (!usernames || usernames.length === 0) {
71
+ throw new MinecraftToolkitError("Body must include a non-empty usernames array", {
72
+ statusCode: 400,
73
+ });
74
+ }
75
+ const delayMs = typeof body.delayMs === "number" ? body.delayMs : undefined;
76
+ return fetchPlayers(usernames, { delayMs });
77
+ });
78
+
79
+ const nameChangeInfoHandler = defineHandler(async (event) => {
80
+ const token = requireAccessToken(event);
81
+ return fetchNameChangeInfo(token);
82
+ });
83
+
84
+ const nameAvailabilityHandler = defineHandler(async (event) => {
85
+ const name = requireParam(event, "name", "Name parameter is required");
86
+ const token = requireAccessToken(event);
87
+ return checkNameAvailability(name, token);
88
+ });
89
+
90
+ const giftCodeValidationHandler = defineHandler(async (event) => {
91
+ const token = requireAccessToken(event);
92
+ const body = (await readBody(event)) ?? {};
93
+ const code = typeof body.code === "string" ? body.code.trim() : "";
94
+ if (!code) {
95
+ throw new MinecraftToolkitError("Gift code is required", { statusCode: 400 });
96
+ }
97
+ const valid = await validateGiftCode(code, token);
98
+ return { code, valid };
99
+ });
100
+
101
+ const blockedServersHandler = defineHandler(async () => fetchBlockedServers());
102
+
103
+ const serverStatusHandler = defineHandler(async (event) => {
104
+ const address = normalizeAddress(
105
+ requireParam(event, "address", "Server address parameter is required"),
106
+ );
107
+ const query = getQuery(event);
108
+ const edition = typeof query.edition === "string" ? query.edition : undefined;
109
+ const port = typeof query.port === "string" ? validatePort(query.port) : undefined;
110
+ const timeoutMs =
111
+ typeof query.timeoutMs === "string" ? Number.parseInt(query.timeoutMs, 10) : undefined;
112
+ const protocolVersion =
113
+ typeof query.protocolVersion === "string"
114
+ ? Number.parseInt(query.protocolVersion, 10)
115
+ : undefined;
116
+
117
+ return fetchServerStatus(address, {
118
+ edition,
119
+ port,
120
+ timeoutMs,
121
+ protocolVersion,
122
+ });
123
+ });
124
+
125
+ return {
126
+ profileHandler,
127
+ skinHandler,
128
+ summaryHandler,
129
+ uuidHandler,
130
+ resolverHandler,
131
+ nameHistoryHandler,
132
+ existsHandler,
133
+ batchHandler,
134
+ nameChangeInfoHandler,
135
+ nameAvailabilityHandler,
136
+ giftCodeValidationHandler,
137
+ blockedServersHandler,
138
+ serverStatusHandler,
139
+ };
140
+ }
141
+
142
+ export function createPlayerApp(options = {}) {
143
+ const app = new H3(options?.app);
144
+ const handlers = createPlayerHandlers();
145
+
146
+ app.get("/player/:username", handlers.profileHandler, {
147
+ meta: { category: "player", resource: "profile" },
148
+ });
149
+
150
+ app.get("/player/:username/skin", handlers.skinHandler, {
151
+ meta: { category: "player", resource: "skin" },
152
+ });
153
+
154
+ app.get("/player/:username/summary", handlers.summaryHandler, {
155
+ meta: { category: "player", resource: "summary" },
156
+ });
157
+
158
+ app.get("/player/:username/uuid", handlers.uuidHandler, {
159
+ meta: { category: "player", resource: "uuid" },
160
+ });
161
+
162
+ app.get("/player/:input/resolve", handlers.resolverHandler, {
163
+ meta: { category: "player", resource: "resolve" },
164
+ });
165
+
166
+ app.get("/player/:uuid/names", handlers.nameHistoryHandler, {
167
+ meta: { category: "player", resource: "name-history" },
168
+ });
169
+
170
+ app.get("/player/:username/exists", handlers.existsHandler, {
171
+ meta: { category: "player", resource: "exists" },
172
+ });
173
+
174
+ app.post("/players/batch", handlers.batchHandler, {
175
+ meta: { category: "player", resource: "batch" },
176
+ });
177
+
178
+ app.get("/account/namechange", handlers.nameChangeInfoHandler, {
179
+ meta: { category: "account", resource: "namechange" },
180
+ });
181
+
182
+ app.get("/account/name/:name/availability", handlers.nameAvailabilityHandler, {
183
+ meta: { category: "account", resource: "name-availability" },
184
+ });
185
+
186
+ app.post("/account/gift-code/validate", handlers.giftCodeValidationHandler, {
187
+ meta: { category: "account", resource: "gift-code" },
188
+ });
189
+
190
+ app.get("/account/blocked-servers", handlers.blockedServersHandler, {
191
+ meta: { category: "account", resource: "blocked-servers" },
192
+ });
193
+
194
+ app.get("/server/:address/status", handlers.serverStatusHandler, {
195
+ meta: { category: "server", resource: "status" },
196
+ });
197
+
198
+ return { app, handlers };
199
+ }
200
+
201
+ export const playerPlugin = definePlugin((app) => {
202
+ const { handlers } = createPlayerApp({ app });
203
+ return handlers;
204
+ });
205
+
206
+ function requireAccessToken(event) {
207
+ const header = event.req?.headers?.get?.("authorization") ?? "";
208
+ if (!header.toLowerCase().startsWith("bearer ")) {
209
+ throw new MinecraftToolkitError("Authorization header with Bearer token is required", {
210
+ statusCode: 401,
211
+ });
212
+ }
213
+ const token = header.slice(7).trim();
214
+ if (!token) {
215
+ throw new MinecraftToolkitError("Access token is required", { statusCode: 401 });
216
+ }
217
+ return token;
218
+ }
@@ -0,0 +1,76 @@
1
+ import { DEFAULT_HEADERS } from "../../constants.js";
2
+ import { MinecraftToolkitError } from "../../errors.js";
3
+ import { fetchJson } from "../../utils/http/index.js";
4
+
5
+ const API_BASE = "https://api.minecraftservices.com";
6
+
7
+ function assertAccessToken(accessToken) {
8
+ if (!accessToken || typeof accessToken !== "string") {
9
+ throw new MinecraftToolkitError("A valid access token is required", { statusCode: 401 });
10
+ }
11
+ return accessToken;
12
+ }
13
+
14
+ function authHeaders(accessToken) {
15
+ return {
16
+ Authorization: `Bearer ${assertAccessToken(accessToken)}`,
17
+ };
18
+ }
19
+
20
+ export async function fetchNameChangeInfo(accessToken) {
21
+ return fetchJson(`${API_BASE}/minecraft/profile/namechange`, {
22
+ headers: authHeaders(accessToken),
23
+ });
24
+ }
25
+
26
+ export async function checkNameAvailability(name, accessToken) {
27
+ if (!name || typeof name !== "string") {
28
+ throw new MinecraftToolkitError("Name is required", { statusCode: 400 });
29
+ }
30
+ return fetchJson(`${API_BASE}/minecraft/profile/name/${encodeURIComponent(name)}/available`, {
31
+ headers: authHeaders(accessToken),
32
+ });
33
+ }
34
+
35
+ export async function validateGiftCode(code, accessToken) {
36
+ if (!code || typeof code !== "string") {
37
+ throw new MinecraftToolkitError("Gift code is required", { statusCode: 400 });
38
+ }
39
+
40
+ const response = await fetch(`${API_BASE}/productvoucher/giftcode/${encodeURIComponent(code)}`, {
41
+ headers: {
42
+ ...DEFAULT_HEADERS,
43
+ ...authHeaders(accessToken),
44
+ },
45
+ });
46
+
47
+ if (response.status === 200 || response.status === 204) {
48
+ return true;
49
+ }
50
+
51
+ if (response.status === 404) {
52
+ return false;
53
+ }
54
+
55
+ throw new MinecraftToolkitError(`Failed to validate gift code ${code}`, {
56
+ statusCode: response.status,
57
+ });
58
+ }
59
+
60
+ export async function fetchBlockedServers() {
61
+ const response = await fetch(`https://sessionserver.mojang.com/blockedservers`, {
62
+ headers: DEFAULT_HEADERS,
63
+ });
64
+
65
+ if (!response.ok) {
66
+ throw new MinecraftToolkitError("Unable to fetch blocked servers", {
67
+ statusCode: response.status,
68
+ });
69
+ }
70
+
71
+ const text = await response.text();
72
+ return text
73
+ .split("\n")
74
+ .map((entry) => entry.trim())
75
+ .filter(Boolean);
76
+ }
@@ -0,0 +1,35 @@
1
+ import { MinecraftToolkitError } from "../../errors.js";
2
+
3
+ const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,16}$/;
4
+ const UUID_HEX = /^[0-9a-fA-F]+$/;
5
+
6
+ export function isValidUsername(username) {
7
+ return typeof username === "string" && USERNAME_REGEX.test(username.trim());
8
+ }
9
+
10
+ export function isUUID(value) {
11
+ if (typeof value !== "string") {
12
+ return false;
13
+ }
14
+ const normalized = value.replace(/-/g, "");
15
+ return normalized.length === 32 && UUID_HEX.test(normalized);
16
+ }
17
+
18
+ export function normalizeUUID(uuid) {
19
+ if (!isUUID(uuid)) {
20
+ throw new MinecraftToolkitError("Invalid UUID", { statusCode: 400 });
21
+ }
22
+ return uuidWithoutDashes(uuid).toLowerCase();
23
+ }
24
+
25
+ export function uuidWithDashes(uuid) {
26
+ const compact = uuidWithoutDashes(uuid);
27
+ return compact.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
28
+ }
29
+
30
+ export function uuidWithoutDashes(uuid) {
31
+ if (typeof uuid !== "string") {
32
+ return uuid;
33
+ }
34
+ return uuid.replace(/-/g, "");
35
+ }