Skip to content

Commit

Permalink
Allow fetching by texture id's
Browse files Browse the repository at this point in the history
  • Loading branch information
TibiNonEst committed Feb 14, 2022
1 parent c258efe commit 5197d4d
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 34 deletions.
5 changes: 4 additions & 1 deletion worker/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export enum RequestedKind {
// IdentityKind determines if the request is for a UUID or a username.
export enum IdentityKind {
Uuid,
Username
Username,
TextureID
}

export enum TextureKind {
Expand Down Expand Up @@ -88,6 +89,8 @@ export function interpretRequest(request: Request): CraftheadRequest | null {
} else if (identity.length === 36) {
identity = identity.replace(/-/g, '')
identityType = IdentityKind.Uuid
} else if (identity.length === 64) {
identityType = IdentityKind.TextureID
} else {
return null
}
Expand Down
94 changes: 61 additions & 33 deletions worker/services/mojang/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class MojangRequestService {
* @param gatherer any promise gatherer
*/
async normalizeRequest(request: CraftheadRequest, gatherer: PromiseGatherer): Promise<CraftheadRequest> {
if (request.identityType === IdentityKind.Uuid) {
if (request.identityType === IdentityKind.Uuid || request.identityType == IdentityKind.TextureID) {
return request;
}

Expand All @@ -61,30 +61,27 @@ export default class MojangRequestService {
* Fetches a texture directly from the Mojang servers. Assumes the request has been normalized already.
*/
private async retrieveTextureDirect(request: CraftheadRequest, gatherer: PromiseGatherer, kind: TextureKind): Promise<Response> {
if (request.identityType == IdentityKind.TextureID) {
const textureResponse = await MojangRequestService.fetchTextureFromId(request.identity);
return MojangRequestService.constructTextureResponse(textureResponse);
}

const rawUuid = fromHex(request.identity);
if (uuidVersion(rawUuid) === 4) {
const lookup = await this.mojangApi.fetchProfile(request.identity, gatherer);
if (lookup.result) {
let textureResponse = await MojangRequestService.fetchTextureFromProfile(lookup.result, kind);
if (textureResponse) {
const buff = await textureResponse.texture.arrayBuffer();
if (buff && buff.byteLength > 0) {
return new Response(buff, {
status: 200,
headers: {
'X-Crafthead-Profile-Cache-Hit': lookup.source,
'X-Crafthead-Skin-Model': textureResponse.model || 'default'
}
});
}
return MojangRequestService.constructTextureResponse(textureResponse, lookup.source)
} else {
return new Response(STEVE_SKIN, {
status: 404,
headers: {
'X-Crafthead-Profile-Cache-Hit': 'not-found',
'X-Crafthead-Skin-Model': 'default'
}
});
}
return new Response(STEVE_SKIN, {
status: 404,
headers: {
'X-Crafthead-Profile-Cache-Hit': 'not-found',
'X-Crafthead-Skin-Model': 'default'
}
});
}
return new Response(STEVE_SKIN, {
status: 404,
Expand All @@ -104,6 +101,27 @@ export default class MojangRequestService {
});
}

private static async constructTextureResponse(textureResponse: TextureResponse, source?: string): Promise<Response> {
const buff = await textureResponse.texture.arrayBuffer();
if (buff && buff.byteLength > 0) {
return new Response(buff, {
status: 200,
headers: {
'X-Crafthead-Profile-Cache-Hit': source || 'miss',
'X-Crafthead-Skin-Model': textureResponse.model || 'default'
}
});
} else {
return new Response(STEVE_SKIN, {
status: 404,
headers: {
'X-Crafthead-Profile-Cache-Hit': 'not-found',
'X-Crafthead-Skin-Model': 'default'
}
});
}
}

async retrieveSkin(request: CraftheadRequest, gatherer: PromiseGatherer): Promise<Response> {
if (request.identity === 'char' || request.identity === 'MHF_Steve') {
// These are special-cased by Minotar.
Expand All @@ -112,6 +130,7 @@ export default class MojangRequestService {

const normalized = await this.normalizeRequest(request, gatherer);
const skin = await this.retrieveTextureDirect(normalized, gatherer, TextureKind.SKIN);

if (skin.status === 404) {
// Offline mode ID (usually when we have a username and the username isn't valid)
const rawUuid = fromHex(normalized.identity);
Expand Down Expand Up @@ -146,28 +165,37 @@ export default class MojangRequestService {
const textureUrl = type === TextureKind.CAPE ? texturesData?.CAPE?.url : texturesData?.SKIN.url;

if (textureUrl) {
const textureResponse = await fetch(textureUrl, {
cf: {
cacheEverything: true,
cacheTtl: 86400
},
headers: {
'User-Agent': 'Crafthead (+https://crafthead.net)'
}
});
if (!textureResponse.ok) {
throw new Error(`Unable to retrieve texture from Mojang, http status ${textureResponse.status}`);
}

console.log("Successfully retrieved texture");
return { texture: textureResponse, model: texturesData?.SKIN?.metadata?.model };
return this.fetchTextureFromUrl(textureUrl);
}
}

console.log("Invalid properties found! Falling back to a default texture.")
return undefined;
}

private static async fetchTextureFromId(id: string): Promise<TextureResponse> {
const url = `https://textures.minecraft.net/texture/${id}`
return this.fetchTextureFromUrl(url);
}

private static async fetchTextureFromUrl(textureUrl: string): Promise<TextureResponse> {
const textureResponse = await fetch(textureUrl, {
cf: {
cacheEverything: true,
cacheTtl: 86400
},
headers: {
'User-Agent': 'Crafthead (+https://crafthead.net)'
}
});
if (!textureResponse.ok) {
throw new Error(`Unable to retrieve texture from Mojang, http status ${textureResponse.status}`);
}

console.log("Successfully retrieved texture");
return { texture: textureResponse, model: "default" };
}

async fetchProfile(request: CraftheadRequest, gatherer: PromiseGatherer): Promise<CacheComputeResult<MojangProfile | null>> {
const normalized = await this.normalizeRequest(request, gatherer);
if (!normalized.identity || uuidVersion(fromHex(normalized.identity)) === 3) {
Expand Down

0 comments on commit 5197d4d

Please sign in to comment.