import JSZip from 'jszip'; import { s8 } from './random'; import axios from 'axios'; import CryptoJS from 'crypto-js'; import { deepClone } from '@meta2d/core'; export const MESH_SUFFIXES = ['.glb', '.l3db']; export const TEMPLATE_SUFFIXES = ['.l3dm']; export const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.svg', '.bmp', '.gif']; export const HDR_SUFFIXES = ['.hdr', '.dds', '.env']; export async function load3d(zip: JSZip, key: string) { const _filename = key.split('/')[0]; const dataStr = await zip.file(key).async('string'); const { data, map } = JSON.parse(dataStr); const newUrlMap: { [oldUrl: string]: string } = {}; await Promise.all( Object.keys(map).map(async (url) => { try { const file = await zip .file(_filename + '/files/' + map[url]) ?.async('arraybuffer'); if (file) { const names = filename(url, true).split('.'); names.splice(-1, 0, s8()); const name = names.join('.'); const newFile = new File([file], name); const suffix = '.' + names[names.length - 1]; let directory = ''; let type = ''; let tags = ''; if (MESH_SUFFIXES.includes(suffix)) { directory = '/3D/模型/未分类'; type = '3D模型'; tags = '我的模型'; } else if (TEMPLATE_SUFFIXES.includes(suffix)) { directory = '/3D/面板/未分类'; type = 'L3DM'; tags = '我的面板'; } else if (IMAGE_SUFFIXES.includes(suffix)) { directory = '/3D/图片/未分类'; type = '图片'; tags = '我的图片'; } else if (HDR_SUFFIXES.includes(suffix)) { directory = '/3D/HDR/未分类'; type = 'HDR'; tags = '我的HDR'; } if (directory) { const res:any = await uploadFile({ file: newFile, directory, type, tags, }); if (res) { newUrlMap[url] = res.url; // uploaded++; return; } } } // failed++; } catch (e) { // failed++; } finally { } }) ); const newData = meta3dReplaceUrl(data, newUrlMap); //上传 const projectData = meta3dCrypto(JSON.stringify(newData)); const res: any = await axios.post('/api/data/3d/add', { data: { data: projectData, scenes: [], updatedCount: 1, }, image: 'xxx', name: newData.name, }); if (res) { return res.id; } else { return ''; } } export function s16() { let chars = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ]; let strs = ''; for (let i = 0; i < 16; i++) { let id = Math.ceil(Math.random() * 61); strs += chars[id]; } return strs; } const a = (word: string, k: string, i: string) => { let srcs = CryptoJS.enc.Utf8.parse(word); let encrypted = CryptoJS.AES.encrypt(srcs, CryptoJS.enc.Utf8.parse(k), { iv: CryptoJS.enc.Utf8.parse(i), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return encrypted.ciphertext.toString().toUpperCase(); }; const c = (word: string): string => { const k = s16().toUpperCase(); const i = s16().toUpperCase(); const d = a(word, k, i); return k + d + i; }; const meta3dCrypto = (data: string) => { return c(data); }; export const getUrlWithoutSearch = (url: string) => { return url.split('?')[0]; }; export function filename(str: string, withSuffix = false) { const paths = getUrlWithoutSearch(str).split('/'); const name = paths[paths.length - 1]; if (withSuffix) { return name; } const i = name.lastIndexOf('.'); if (i === -1) { return name; } return name.substring(0, i); } export const uploadFile = async (info: { file: File; directory: string; type: UploadFileType; tags?: string; remarks?: string; name?: string; }) => { const { file, directory, type, tags = '', remarks = '', name = '' } = info; const formData = new FormData(); formData.append('file', file); formData.append('directory', directory); formData.append('type', type); formData.append('tags', tags); formData.append('remarks', remarks); formData.append('shared', 'true'); if (name) { formData.append('name', name); } const res = (await axios.post( '/api/file/upload', formData )) as ResponseResult; if (res.error) { } return res; }; export type UploadFileType = | '文档' | '图片' | '3D模型' | '视频' | '音乐' | '其他' | string; export interface ResponseResult { success: boolean; data?: T; error?: string; detail?: string; } export interface FileData { createdAt: string; directory: string; fullname: string; id: string; name: string; ownerId: string; ownerName: string; shared: boolean; size: number; tags: string[]; type: UploadFileType; updatedAt: string; url: string; } const b = (word: string, k: string, i: string) => { let encryptedHexStr = CryptoJS.enc.Hex.parse(word); let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr); let decrypt = CryptoJS.AES.decrypt(srcs, CryptoJS.enc.Utf8.parse(k), { iv: CryptoJS.enc.Utf8.parse(i), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8); return decryptedStr.toString(); }; const d = (word: string): string => { const k = word.substring(0, 16); const i = word.substring(word.length - 16); return b(word.substring(16, word.length - 16), k, i); }; const parseData = (data: Partial | string): Partial => { if (typeof data === 'string') { data = data.startsWith('{') ? data : d(data); return JSON.parse(data); } return data; }; export function filepath(str: string) { const paths = str.split('/'); paths[paths.length - 1] = ''; return paths.join('/'); } const meta3dReplaceUrl = ( data: any | string, urlMap: { [url: string]: string } ) => { const _data = parseData(data); const { scenes } = _data; const transUrl = (url: string): string => { if (url in urlMap) { return urlMap[url]; } return url; }; for (const sceneData of scenes) { if (!sceneData) { continue; } const { nodes = [], scene = {}, glbMap = {}, textures = {}, materials = {}, DOMDatas = [], } = sceneData; Object.keys(glbMap).forEach((glbId) => { const { url, name } = glbMap[glbId]; const fullUrl = url + name; if (fullUrl in urlMap) { glbMap[glbId].url = filepath(urlMap[fullUrl]); glbMap[glbId].name = filename(urlMap[fullUrl], true); } }); for (const node of [ scene, ...nodes, ...DOMDatas, ...Object.keys(materials).map((id) => materials[id]), ...Object.keys(textures).map((id) => textures[id]), ]) { convertResourceAddress(node, transUrl); } } return _data; }; const urlProps = [ 'url', 'imageSource', 'skyboxUrl', 'hdrUrl', 'colorGradingTexture', 'source', 'backgroundImage', ]; function convertResourceAddress( data: any, transFn: (url: string) => any, reset = true ) { for (const urlProp of urlProps) { if (urlProp in data === false) { continue; } const url = data[urlProp] || ''; const newUrl = transFn(url); if (reset) { data[urlProp] = newUrl; } } if (data.contents) { data.contents.forEach((content: any) => convertResourceAddress(content, transFn, reset) ); } if (data.__initOption) { convertResourceAddress(data.__initOption, transFn, reset); } if (data.children) { data.children.forEach((child: any) => convertResourceAddress(child, transFn, reset) ); } }