Spaces:
Sleeping
Sleeping
| 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(); | |