StyleGPT-milestone2 / src /utils /readyPlayerMe.ts
nexusbert's picture
pushed
1c4d53b
import axios from "axios";
import dotenv from "dotenv";
import FormData from "form-data";
dotenv.config();
const READY_API_KEY = process.env.READY_API_KEY;
const READY_API_BASE = "https://api.readyplayer.me/v1";
const READY_MODELS_BASE = "https://models.readyplayer.me";
const READY_AVATARS_BASE = "https://avatars.readyplayer.me";
if (!READY_API_KEY) {
console.warn("READY_API_KEY not found in environment variables");
}
const getHeaders = (): Record<string, string> => ({
"X-API-Key": READY_API_KEY || "",
"Content-Type": "application/json",
});
export interface ReadyPlayerMeUser {
id: string;
applicationIds: string[];
partners: string[];
createdAt: string;
updatedAt: string;
}
export interface ReadyPlayerMeAsset {
id: string;
name: string;
type: string;
gender: string;
modelUrl: string;
iconUrl: string;
organizationId: string;
locked: boolean;
applications?: any[];
hasApps: boolean;
createdAt: string;
updatedAt: string;
}
export interface ReadyPlayerMeToken {
token: string;
}
export interface ReadyPlayerMeAvatarMetadata {
id: string;
url?: string;
[key: string]: any;
}
export class ReadyPlayerMeClient {
private apiKey: string;
private applicationId?: string;
constructor(applicationId?: string) {
this.apiKey = READY_API_KEY || "";
this.applicationId = applicationId;
}
async createGuestUser(applicationId: string): Promise<ReadyPlayerMeUser | null> {
try {
const response = await axios.post<{ data: ReadyPlayerMeUser }>(
`${READY_API_BASE}/users`,
{
data: {
applicationId,
},
},
{
headers: getHeaders(),
}
);
return response.data.data;
} catch (error: any) {
console.error("Error creating Ready Player Me guest user:", error.response?.data || error.message);
return null;
}
}
async getAuthToken(userId: string, partner: string): Promise<string | null> {
try {
const response = await axios.get<{ data: ReadyPlayerMeToken }>(
`${READY_API_BASE}/auth/token`,
{
params: { userId, partner },
headers: getHeaders(),
}
);
return response.data.data.token;
} catch (error: any) {
console.error("Error getting Ready Player Me token:", error.response?.data || error.message);
return null;
}
}
async getAvatarGLB(avatarId: string, options?: {
quality?: "low" | "medium" | "high" | "ultra";
lod?: number;
textureAtlas?: number | "none";
textureFormat?: "webp" | "jpeg" | "png";
useDracoMeshCompression?: boolean;
}): Promise<string> {
const params = new URLSearchParams();
if (options?.quality) params.append("quality", options.quality);
if (options?.lod !== undefined) params.append("lod", options.lod.toString());
if (options?.textureAtlas) params.append("textureAtlas", options.textureAtlas.toString());
if (options?.textureFormat) params.append("textureFormat", options.textureFormat);
if (options?.useDracoMeshCompression) params.append("useDracoMeshCompression", "true");
const queryString = params.toString();
return `${READY_AVATARS_BASE}/${avatarId}.glb${queryString ? `?${queryString}` : ""}`;
}
async getAvatar2DRender(avatarId: string, options?: {
size?: number;
quality?: number;
camera?: "portrait" | "fullbody" | "fit";
background?: string;
expression?: string;
pose?: string;
}): Promise<string> {
const params = new URLSearchParams();
if (options?.size) params.append("size", options.size.toString());
if (options?.quality) params.append("quality", options.quality.toString());
if (options?.camera) params.append("camera", options.camera);
if (options?.background) params.append("background", options.background);
if (options?.expression) params.append("expression", options.expression);
if (options?.pose) params.append("pose", options.pose);
const queryString = params.toString();
return `${READY_MODELS_BASE}/${avatarId}.png${queryString ? `?${queryString}` : ""}`;
}
async getAvatarMetadata(avatarId: string): Promise<ReadyPlayerMeAvatarMetadata | null> {
try {
const response = await axios.get<{ data: ReadyPlayerMeAvatarMetadata }>(
`${READY_MODELS_BASE}/${avatarId}.json`,
{
headers: getHeaders(),
}
);
return response.data.data;
} catch (error: any) {
console.error("Error getting avatar metadata:", error.response?.data || error.message);
return null;
}
}
async uploadAssetFile(fileBuffer: Buffer, filename: string, mimetype: string): Promise<string | null> {
try {
const formData = new FormData();
formData.append("file", fileBuffer, {
filename,
contentType: mimetype,
});
const headers = {
...getHeaders(),
...formData.getHeaders(),
};
console.log(`[Ready Player Me] Uploading file: ${filename} (${fileBuffer.length} bytes, ${mimetype})`);
const response = await axios.post<{ data: { url: string } }>(
`${READY_API_BASE}/temporary-media`,
formData,
{
headers,
maxContentLength: Infinity,
maxBodyLength: Infinity,
}
);
if (response.data?.data?.url) {
console.log(`[Ready Player Me] File uploaded successfully: ${response.data.data.url}`);
return response.data.data.url;
} else {
console.error("[Ready Player Me] Upload response missing URL:", response.data);
return null;
}
} catch (error: any) {
console.error("[Ready Player Me] Error uploading asset file:");
console.error(" Status:", error.response?.status);
console.error(" Status Text:", error.response?.statusText);
console.error(" Response Data:", JSON.stringify(error.response?.data, null, 2));
console.error(" Error Message:", error.message);
if (error.response?.data) {
console.error(" Full Error:", JSON.stringify(error.response.data, null, 2));
}
return null;
}
}
async createAsset(data: {
name: string;
type: string;
gender: "male" | "female" | "neutral";
modelUrl: string;
iconUrl: string;
organizationId: string;
locked?: boolean;
applications?: Array<{
id: string;
organizationId: string;
isVisibleInEditor?: boolean;
}>;
}): Promise<ReadyPlayerMeAsset | null> {
try {
console.log(`[Ready Player Me] Creating asset: ${data.name} (type: ${data.type}, gender: ${data.gender})`);
const response = await axios.post<{ data: ReadyPlayerMeAsset }>(
`${READY_API_BASE}/assets`,
{ data },
{
headers: getHeaders(),
}
);
if (response.data?.data) {
console.log(`[Ready Player Me] Asset created successfully: ${response.data.data.id}`);
return response.data.data;
} else {
console.error("[Ready Player Me] Create asset response missing data:", response.data);
return null;
}
} catch (error: any) {
console.error("[Ready Player Me] Error creating asset:");
console.error(" Status:", error.response?.status);
console.error(" Status Text:", error.response?.statusText);
console.error(" Response Data:", JSON.stringify(error.response?.data, null, 2));
console.error(" Error Message:", error.message);
if (error.response?.data) {
console.error(" Full Error:", JSON.stringify(error.response.data, null, 2));
}
return null;
}
}
async listAssets(filters?: {
type?: string[];
gender?: string[];
name?: string;
organizationId?: string;
applicationIds?: string[];
limit?: number;
page?: number;
}): Promise<{ data: ReadyPlayerMeAsset[]; pagination: any } | null> {
try {
const params = new URLSearchParams();
if (filters?.type) {
filters.type.forEach(t => params.append("type", t));
}
if (filters?.gender) {
filters.gender.forEach(g => params.append("gender", g));
}
if (filters?.name) params.append("name", filters.name);
if (filters?.organizationId) params.append("organizationId", filters.organizationId);
if (filters?.applicationIds) {
filters.applicationIds.forEach(id => params.append("applicationIds", id));
}
if (filters?.limit) params.append("limit", filters.limit.toString());
if (filters?.page) params.append("page", filters.page.toString());
const headers: Record<string, string> = { ...getHeaders() };
if (this.applicationId) {
headers["X-APP-ID"] = this.applicationId;
}
const response = await axios.get<{ data: ReadyPlayerMeAsset[]; pagination: any }>(
`${READY_API_BASE}/assets?${params.toString()}`,
{ headers }
);
return response.data;
} catch (error: any) {
console.error("Error listing assets:", error.response?.data || error.message);
return null;
}
}
async updateAsset(assetId: string, data: {
name?: string;
type?: string;
gender?: "male" | "female" | "neutral";
modelUrl?: string;
iconUrl?: string;
locked?: boolean;
applications?: Array<{
id: string;
organizationId: string;
isVisibleInEditor?: boolean;
}>;
}): Promise<ReadyPlayerMeAsset | null> {
try {
const response = await axios.patch<{ data: ReadyPlayerMeAsset }>(
`${READY_API_BASE}/assets/${assetId}`,
{ data },
{
headers: getHeaders(),
}
);
return response.data.data;
} catch (error: any) {
console.error("Error updating asset:", error.response?.data || error.message);
return null;
}
}
async equipAsset(avatarId: string, assetId: string): Promise<boolean> {
try {
await axios.put(
`${READY_API_BASE}/avatars/${avatarId}/equip`,
{
data: {
assetId,
},
},
{
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error equipping asset:", error.response?.data || error.message);
return false;
}
}
async unequipAsset(avatarId: string, assetId: string): Promise<boolean> {
try {
await axios.put(
`${READY_API_BASE}/avatars/${avatarId}/unequip`,
{
data: {
assetId,
},
},
{
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error unequipping asset:", error.response?.data || error.message);
return false;
}
}
async addAssetToApplication(assetId: string, applicationId: string, isVisibleInEditor: boolean = true): Promise<boolean> {
try {
await axios.post(
`${READY_API_BASE}/assets/${assetId}/application`,
{
data: {
applicationId,
isVisibleInEditor,
},
},
{
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error adding asset to application:", error.response?.data || error.message);
return false;
}
}
async removeAssetFromApplication(assetId: string, applicationId: string): Promise<boolean> {
try {
await axios.delete(
`${READY_API_BASE}/assets/${assetId}/application`,
{
data: {
applicationId,
},
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error removing asset from application:", error.response?.data || error.message);
return false;
}
}
async unlockAssetForUser(assetId: string, userId: string): Promise<boolean> {
try {
await axios.put(
`${READY_API_BASE}/assets/${assetId}/unlock`,
{
data: {
userId,
},
},
{
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error unlocking asset:", error.response?.data || error.message);
return false;
}
}
async lockAssetForUser(assetId: string, userId: string): Promise<boolean> {
try {
await axios.put(
`${READY_API_BASE}/assets/${assetId}/lock`,
{
data: {
userId,
},
},
{
headers: getHeaders(),
}
);
return true;
} catch (error: any) {
console.error("Error locking asset:", error.response?.data || error.message);
return false;
}
}
}
export const readyPlayerMeClient = new ReadyPlayerMeClient();