Skip to content

Instantly share code, notes, and snippets.

@kirakira-dev
Created May 5, 2026 11:34
Show Gist options
  • Select an option

  • Save kirakira-dev/9342dafd801b0709c6c33e83979cd897 to your computer and use it in GitHub Desktop.

Select an option

Save kirakira-dev/9342dafd801b0709c6c33e83979cd897 to your computer and use it in GitHub Desktop.
A tampermonkey plugin to download models from Meshy for free
// ==UserScript==
// @name meshy model dwonloader
// @namespace https://kirakira.cloud
// @version 1.0.0
// @description meshy model downloader, bypass paywall, made by the poor for the poor
// @author kirakira
// @match https://www.meshy.ai/*
// @run-at document-start
// @grant none
// ==/UserScript==
// this plugin was last tested on 2026 May 5, worked completly fine
// note that some formats are buggy
// i recommend obj
(function () {
'use strict';
if (window.__meshyEasyDownloadTMInstalled) return;
window.__meshyEasyDownloadTMInstalled = true;
pageScript();
function pageScript() {
'use strict';
const DEBUG_MODE = true;
const MODEL_CACHE_KEY = 'meshy_easy_download_models_v3';
const MAX_MODEL_CACHE = 100;
const capturedGlbByTask = new Map();
const capturedGlbByUrl = new Map();
const modelUrlByTask = new Map();
const captureSignatures = new Set();
let lastCapturedGlb = null;
let lastCapturedTaskId = null;
let lastSeenTaskId = null;
const CHUNK_TYPE_JSON = 0x4e4f534a;
const CHUNK_TYPE_BIN = 0x004e4942;
const GLB_MAGIC = 0x46546c67;
const COMPONENT_TYPE_BYTES = {
5120: 1,
5121: 1,
5122: 2,
5123: 2,
5125: 4,
5126: 4
};
const TYPE_COMPONENTS = {
SCALAR: 1,
VEC2: 2,
VEC3: 3,
VEC4: 4,
MAT2: 4,
MAT3: 9,
MAT4: 16
};
const ARRAY_CTORS = {
5120: Int8Array,
5121: Uint8Array,
5122: Int16Array,
5123: Uint16Array,
5125: Uint32Array,
5126: Float32Array
};
function log(...args) {
if (DEBUG_MODE) {
console.log('[meshy-tm]', ...args);
}
}
function publishDebugState() {
try {
window.__meshyEasyDownloadState = {
lastSeenTaskId,
lastCapturedTaskId,
capturedTaskCount: capturedGlbByTask.size,
capturedModelUrlCount: capturedGlbByUrl.size,
lastCapturedSize: lastCapturedGlb ? lastCapturedGlb.byteLength : 0
};
} catch {}
}
function slugify(text) {
return (text || 'model')
.replace(/[^a-zA-Z0-9\s]/g, '')
.replace(/\s+/g, '-')
.replace(/^-+|-+$/g, '')
.substring(0, 60) || 'model';
}
function formatSize(bytes) {
if (!bytes || bytes <= 0) return '';
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
if (bytes >= 1024) return `${Math.round(bytes / 1024)}KB`;
return `${bytes}B`;
}
function extractTaskIdFromUrl(path = window.location.pathname) {
const match = path.match(/\/workspace\/([a-f0-9-]+)/i);
return match ? match[1] : null;
}
function currentTaskId() {
return extractTaskIdFromUrl();
}
function normalizeTaskId(taskId) {
return taskId || currentTaskId() || lastSeenTaskId || null;
}
function canonicalModelUrl(url) {
if (!url) return '';
try {
const parsed = new URL(url, window.location.origin);
return `${parsed.origin}${parsed.pathname}`;
} catch {
return url;
}
}
function loadModels() {
try {
const raw = localStorage.getItem(MODEL_CACHE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch (error) {
log('model cache parse failed:', error.message);
return {};
}
}
function saveModels(models) {
try {
localStorage.setItem(MODEL_CACHE_KEY, JSON.stringify(models));
} catch (error) {
log('model cache save failed:', error.message);
}
}
function getModel(taskId) {
if (!taskId) return null;
const models = loadModels();
return models[taskId] || null;
}
function saveModel(taskId, model) {
if (!taskId || !model) return;
const models = loadModels();
models[taskId] = { ...model, ts: Date.now() };
const keys = Object.keys(models);
if (keys.length > MAX_MODEL_CACHE) {
keys
.sort((a, b) => (models[a]?.ts || 0) - (models[b]?.ts || 0))
.slice(0, keys.length - MAX_MODEL_CACHE)
.forEach((key) => delete models[key]);
}
saveModels(models);
}
async function fetchSizeText(url) {
if (!url) return '';
try {
const res = await fetch(url, { method: 'HEAD' });
const size = Number.parseInt(res.headers.get('content-length') || '0', 10);
return formatSize(size);
} catch {
return '';
}
}
function isGLBBuffer(buffer) {
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < 12) return false;
try {
const view = new DataView(buffer);
return view.getUint32(0, true) === GLB_MAGIC;
} catch {
return false;
}
}
function isMeshyEncryptedBuffer(buffer) {
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < 8) return false;
try {
const magic = new TextDecoder().decode(new Uint8Array(buffer, 0, 8));
return magic.startsWith('MESHY.AI');
} catch {
return false;
}
}
function glbSignature(buffer) {
const head = Array.from(new Uint8Array(buffer, 0, Math.min(16, buffer.byteLength)));
return `${buffer.byteLength}:${head.join(',')}`;
}
function arrayBufferFromUnknown(value) {
if (value instanceof ArrayBuffer) {
return value.slice(0);
}
if (ArrayBuffer.isView(value)) {
return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
}
return null;
}
function cacheGlbBuffer(taskId, name, buffer, source) {
if (!isGLBBuffer(buffer)) return;
const normalizedTaskId = normalizeTaskId(taskId);
const signature = glbSignature(buffer);
if (captureSignatures.has(signature)) return;
captureSignatures.add(signature);
const cloned = buffer.slice(0);
lastCapturedGlb = cloned;
lastCapturedTaskId = normalizedTaskId;
if (normalizedTaskId) {
capturedGlbByTask.set(normalizedTaskId, cloned);
}
const knownUrl = normalizedTaskId ? modelUrlByTask.get(normalizedTaskId) : '';
if (knownUrl) {
capturedGlbByUrl.set(canonicalModelUrl(knownUrl), cloned);
}
log(
`captured decrypted GLB from ${source}`,
normalizedTaskId ? `(task ${normalizedTaskId})` : '(task unknown)',
formatSize(cloned.byteLength)
);
if (normalizedTaskId) {
const existing = getModel(normalizedTaskId) || {};
saveModel(normalizedTaskId, {
url: existing.url || '',
name: name || existing.name || 'model',
size: existing.size || formatSize(cloned.byteLength)
});
injectButtons(normalizedTaskId, getModel(normalizedTaskId));
} else {
const activeTask = currentTaskId();
if (activeTask) {
injectButtons(activeTask, getModel(activeTask));
}
}
publishDebugState();
}
function downloadBuffer(buffer, filename, mimeType = 'application/octet-stream') {
const blob = new Blob([buffer], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
function downloadText(text, filename) {
downloadBuffer(new TextEncoder().encode(text), filename, 'text/plain;charset=utf-8');
}
function readComponent(view, offset, componentType) {
switch (componentType) {
case 5120: return view.getInt8(offset);
case 5121: return view.getUint8(offset);
case 5122: return view.getInt16(offset, true);
case 5123: return view.getUint16(offset, true);
case 5125: return view.getUint32(offset, true);
case 5126: return view.getFloat32(offset, true);
default: throw new Error(`unsupported component type ${componentType}`);
}
}
function parseGLB(buffer) {
const view = new DataView(buffer);
if (view.byteLength < 20) throw new Error('invalid GLB: too small');
const magic = view.getUint32(0, true);
if (magic !== GLB_MAGIC) throw new Error('invalid GLB: bad magic');
const version = view.getUint32(4, true);
if (version !== 2) throw new Error(`unsupported GLB version ${version}`);
const declaredLength = view.getUint32(8, true);
if (declaredLength > buffer.byteLength) throw new Error('invalid GLB: truncated');
let offset = 12;
let json = null;
let bin = null;
while (offset + 8 <= declaredLength) {
const chunkLength = view.getUint32(offset, true);
const chunkType = view.getUint32(offset + 4, true);
offset += 8;
if (offset + chunkLength > buffer.byteLength) {
throw new Error('invalid GLB: bad chunk size');
}
if (chunkType === CHUNK_TYPE_JSON) {
const jsonBytes = new Uint8Array(buffer, offset, chunkLength);
const jsonText = new TextDecoder().decode(jsonBytes).replace(/\u0000+$/g, '');
json = JSON.parse(jsonText);
} else if (chunkType === CHUNK_TYPE_BIN) {
bin = new Uint8Array(buffer, offset, chunkLength);
}
offset += chunkLength;
}
if (!json) throw new Error('invalid GLB: missing JSON chunk');
if (!bin) bin = new Uint8Array();
return { json, bin };
}
function readAccessor(json, bin, accessorIndex) {
const accessor = json.accessors?.[accessorIndex];
if (!accessor) throw new Error(`missing accessor ${accessorIndex}`);
if (accessor.sparse) throw new Error('sparse accessors are not supported');
const bufferView = json.bufferViews?.[accessor.bufferView];
if (!bufferView) throw new Error(`missing bufferView for accessor ${accessorIndex}`);
if ((bufferView.buffer || 0) !== 0) throw new Error('external buffers are not supported');
const componentType = accessor.componentType;
const componentBytes = COMPONENT_TYPE_BYTES[componentType];
const components = TYPE_COMPONENTS[accessor.type];
const ctor = ARRAY_CTORS[componentType];
if (!componentBytes || !components || !ctor) {
throw new Error(`unsupported accessor format (${componentType}, ${accessor.type})`);
}
const count = accessor.count;
const packedStride = componentBytes * components;
const byteStride = bufferView.byteStride || packedStride;
const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
const out = new ctor(count * components);
if (byteStride === packedStride) {
const src = new ctor(bin.buffer, bin.byteOffset + byteOffset, count * components);
out.set(src);
return out;
}
const dv = new DataView(bin.buffer, bin.byteOffset + byteOffset, count * byteStride);
for (let i = 0; i < count; i++) {
const rowOffset = i * byteStride;
for (let c = 0; c < components; c++) {
out[i * components + c] = readComponent(dv, rowOffset + c * componentBytes, componentType);
}
}
return out;
}
function triangulate(indices, mode) {
const src = Array.from(indices);
if (mode === 4 || mode === undefined) {
if (src.length % 3 !== 0) throw new Error('triangle index count is not divisible by 3');
return src;
}
const out = [];
if (mode === 5) {
for (let i = 0; i < src.length - 2; i++) {
const a = src[i];
const b = src[i + 1];
const c = src[i + 2];
if (a === b || b === c || a === c) continue;
if (i % 2 === 0) out.push(a, b, c);
else out.push(b, a, c);
}
return out;
}
if (mode === 6) {
for (let i = 1; i < src.length - 1; i++) {
const a = src[0];
const b = src[i];
const c = src[i + 1];
if (a === b || b === c || a === c) continue;
out.push(a, b, c);
}
return out;
}
throw new Error(`unsupported primitive mode ${mode}`);
}
function primitiveIndices(json, bin, primitive, vertexCount) {
if (primitive.indices === undefined) {
return Array.from({ length: vertexCount }, (_, i) => i);
}
const idx = readAccessor(json, bin, primitive.indices);
return Array.from(idx, (n) => Number(n));
}
function collectMeshPrimitives(json, bin) {
const out = [];
for (const mesh of json.meshes || []) {
for (const primitive of mesh.primitives || []) {
const positionAccessor = primitive.attributes?.POSITION;
if (positionAccessor === undefined) continue;
const positions = readAccessor(json, bin, positionAccessor);
const vertexCount = json.accessors[positionAccessor].count;
let normals = null;
if (primitive.attributes?.NORMAL !== undefined) {
normals = readAccessor(json, bin, primitive.attributes.NORMAL);
if (normals.length / 3 !== vertexCount) normals = null;
}
let uvs = null;
if (primitive.attributes?.TEXCOORD_0 !== undefined) {
uvs = readAccessor(json, bin, primitive.attributes.TEXCOORD_0);
if (uvs.length / 2 !== vertexCount) uvs = null;
}
const mode = primitive.mode === undefined ? 4 : primitive.mode;
const idx = primitiveIndices(json, bin, primitive, vertexCount);
const triangles = triangulate(idx, mode);
out.push({ positions, normals, uvs, triangles, vertexCount });
}
}
if (out.length === 0) throw new Error('no mesh primitives found');
return out;
}
function calculateNormal(v0, v1, v2) {
const ax = v1[0] - v0[0];
const ay = v1[1] - v0[1];
const az = v1[2] - v0[2];
const bx = v2[0] - v0[0];
const by = v2[1] - v0[1];
const bz = v2[2] - v0[2];
const nx = ay * bz - az * by;
const ny = az * bx - ax * bz;
const nz = ax * by - ay * bx;
const len = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
return [nx / len, ny / len, nz / len];
}
function glbToStlBuffer(buffer) {
const { json, bin } = parseGLB(buffer);
const primitives = collectMeshPrimitives(json, bin);
let triangleCount = 0;
for (const prim of primitives) {
triangleCount += prim.triangles.length / 3;
}
const out = new ArrayBuffer(84 + triangleCount * 50);
const view = new DataView(out);
const header = new Uint8Array(out, 0, 80);
const headerText = 'binary stl - meshy easy download (tampermonkey)';
for (let i = 0; i < headerText.length && i < 80; i++) {
header[i] = headerText.charCodeAt(i);
}
view.setUint32(80, triangleCount, true);
let offset = 84;
for (const prim of primitives) {
const pos = prim.positions;
for (let i = 0; i < prim.triangles.length; i += 3) {
const a = prim.triangles[i] * 3;
const b = prim.triangles[i + 1] * 3;
const c = prim.triangles[i + 2] * 3;
const v0 = [pos[a], pos[a + 1], pos[a + 2]];
const v1 = [pos[b], pos[b + 1], pos[b + 2]];
const v2 = [pos[c], pos[c + 1], pos[c + 2]];
const normal = calculateNormal(v0, v1, v2);
view.setFloat32(offset, normal[0], true); offset += 4;
view.setFloat32(offset, normal[1], true); offset += 4;
view.setFloat32(offset, normal[2], true); offset += 4;
for (const v of [v0, v1, v2]) {
view.setFloat32(offset, v[0], true); offset += 4;
view.setFloat32(offset, v[1], true); offset += 4;
view.setFloat32(offset, v[2], true); offset += 4;
}
view.setUint16(offset, 0, true); offset += 2;
}
}
return out;
}
function glbToObjText(buffer, objectName) {
const { json, bin } = parseGLB(buffer);
const primitives = collectMeshPrimitives(json, bin);
const lines = [];
lines.push('# generated by Meshy.ai Easy Download (Tampermonkey)');
lines.push(`o ${slugify(objectName || 'model')}`);
let vertexOffset = 0;
let uvOffset = 0;
let normalOffset = 0;
for (const prim of primitives) {
const positions = prim.positions;
const uvs = prim.uvs;
const normals = prim.normals;
const hasUV = Boolean(uvs);
const hasNormal = Boolean(normals);
for (let i = 0; i < prim.vertexCount; i++) {
const p = i * 3;
lines.push(`v ${positions[p]} ${positions[p + 1]} ${positions[p + 2]}`);
}
if (hasUV) {
for (let i = 0; i < prim.vertexCount; i++) {
const t = i * 2;
lines.push(`vt ${uvs[t]} ${1 - uvs[t + 1]}`);
}
}
if (hasNormal) {
for (let i = 0; i < prim.vertexCount; i++) {
const n = i * 3;
lines.push(`vn ${normals[n]} ${normals[n + 1]} ${normals[n + 2]}`);
}
}
for (let i = 0; i < prim.triangles.length; i += 3) {
const a = prim.triangles[i] + 1;
const b = prim.triangles[i + 1] + 1;
const c = prim.triangles[i + 2] + 1;
const va = vertexOffset + a;
const vb = vertexOffset + b;
const vc = vertexOffset + c;
if (hasUV && hasNormal) {
const ta = uvOffset + a;
const tb = uvOffset + b;
const tc = uvOffset + c;
const na = normalOffset + a;
const nb = normalOffset + b;
const nc = normalOffset + c;
lines.push(`f ${va}/${ta}/${na} ${vb}/${tb}/${nb} ${vc}/${tc}/${nc}`);
} else if (hasUV) {
const ta = uvOffset + a;
const tb = uvOffset + b;
const tc = uvOffset + c;
lines.push(`f ${va}/${ta} ${vb}/${tb} ${vc}/${tc}`);
} else if (hasNormal) {
const na = normalOffset + a;
const nb = normalOffset + b;
const nc = normalOffset + c;
lines.push(`f ${va}//${na} ${vb}//${nb} ${vc}//${nc}`);
} else {
lines.push(`f ${va} ${vb} ${vc}`);
}
}
vertexOffset += prim.vertexCount;
if (hasUV) uvOffset += prim.vertexCount;
if (hasNormal) normalOffset += prim.vertexCount;
}
lines.push('');
return lines.join('\n');
}
function cloneIfGlb(buffer) {
if (buffer && isGLBBuffer(buffer)) {
return buffer.slice(0);
}
return null;
}
function fallbackCapturedGlb(taskId, modelUrl) {
const candidates = [];
const normalizedTaskId = normalizeTaskId(taskId);
if (taskId) candidates.push(capturedGlbByTask.get(taskId));
if (normalizedTaskId && normalizedTaskId !== taskId) {
candidates.push(capturedGlbByTask.get(normalizedTaskId));
}
if (modelUrl) {
candidates.push(capturedGlbByUrl.get(canonicalModelUrl(modelUrl)));
}
if (lastCapturedTaskId && normalizedTaskId && lastCapturedTaskId === normalizedTaskId) {
candidates.push(lastCapturedGlb);
}
candidates.push(lastCapturedGlb);
if (capturedGlbByTask.size === 1) {
candidates.push(capturedGlbByTask.values().next().value);
}
for (const buffer of candidates) {
const cloned = cloneIfGlb(buffer);
if (cloned) return cloned;
}
return null;
}
async function resolveGLBBuffer(taskId, modelUrl) {
const fallback = fallbackCapturedGlb(taskId, modelUrl);
if (fallback) return fallback;
if (!modelUrl) {
throw new Error('no model URL found yet - open the model once, then retry');
}
const res = await fetch(modelUrl);
const buffer = await res.arrayBuffer();
if (isGLBBuffer(buffer)) {
return buffer;
}
if (isMeshyEncryptedBuffer(buffer)) {
throw new Error('model URL is encrypted and no decrypted buffer was captured yet. Open the model preview, wait 2-3 seconds, then retry.');
}
throw new Error('downloaded data is not a valid GLB');
}
async function downloadAs(format, taskId, model, filename) {
try {
const glbBuffer = await resolveGLBBuffer(taskId, model?.url);
if (format === 'glb') {
downloadBuffer(glbBuffer, filename, 'model/gltf-binary');
return;
}
if (format === 'stl') {
const stlBuffer = glbToStlBuffer(glbBuffer);
downloadBuffer(stlBuffer, filename, 'model/stl');
return;
}
if (format === 'obj') {
const objText = glbToObjText(glbBuffer, model?.name || 'model');
downloadText(objText, filename);
return;
}
} catch (error) {
log(`${format} export failed:`, error.message);
alert(`${format.toUpperCase()} export failed: ${error.message}`);
}
}
function findDownloadButton() {
const path = document.querySelector('path[d^="M4.933 6.272h1.06V2.938"]');
return path ? path.closest('button') : null;
}
function buttonColors(format) {
if (format === 'stl') return ['#ec4899', '#f472b6'];
if (format === 'obj') return ['#0ea5e9', '#38bdf8'];
return ['#8b5cf6', '#a855f7'];
}
function createButton(format, taskId, model) {
const [from, to] = buttonColors(format);
const safeName = slugify(model?.name || 'model');
const filename = `${safeName}_${taskId}.${format}`;
const extraSize = format === 'glb' && model?.size ? ` ${model.size}` : '';
const anchor = document.createElement('a');
anchor.href = '#';
anchor.className = 'meshy-tm-btn';
anchor.dataset.taskId = taskId;
anchor.dataset.format = format;
anchor.style.marginLeft = '8px';
anchor.style.textDecoration = 'none';
anchor.innerHTML = `
<button type="button" style="background: linear-gradient(135deg, ${from} 0%, ${to} 100%); border: none;"
class="group/button inline-flex items-center justify-center font-medium whitespace-nowrap transition duration-100 ease-out select-none px-3 h-7 rounded-lg gap-1.5 text-xs active:opacity-70 text-white hover:opacity-90 cursor-pointer">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.933 6.272h1.06V2.938c0-.366.3-.666.667-.666h2.667c.366 0 .666.3.666.666v3.334h1.06c.594 0 .894.72.474 1.14l-3.06 3.06a.665.665 0 0 1-.94 0l-3.06-3.06c-.42-.42-.127-1.14.466-1.14Zm-1.6 6.395c0 .366.3.666.667.666h8c.367 0 .667-.3.667-.666 0-.367-.3-.667-.667-.667H4c-.367 0-.667.3-.667.667Z" fill="white"/>
</svg>
<span>.${format}${extraSize}</span>
</button>
`;
anchor.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
downloadAs(format, taskId, model, filename);
};
return anchor;
}
function injectButtons(taskId, model, retries = 6) {
const downloadButton = findDownloadButton();
if (!downloadButton) {
if (retries > 0) {
setTimeout(() => injectButtons(taskId, model, retries - 1), 500);
}
return;
}
const wrapper = downloadButton.closest('div.relative.inline-flex');
if (!wrapper || !wrapper.parentNode) return;
document.querySelectorAll('.meshy-tm-btn').forEach((btn) => btn.remove());
const normalizedModel = model || {
name: 'model',
url: '',
size: capturedGlbByTask.get(taskId) ? formatSize(capturedGlbByTask.get(taskId).byteLength) : ''
};
const glbBtn = createButton('glb', taskId, normalizedModel);
const stlBtn = createButton('stl', taskId, normalizedModel);
const objBtn = createButton('obj', taskId, normalizedModel);
wrapper.parentNode.insertBefore(glbBtn, wrapper.nextSibling);
wrapper.parentNode.insertBefore(stlBtn, glbBtn.nextSibling);
wrapper.parentNode.insertBefore(objBtn, stlBtn.nextSibling);
}
function findModelUrlInPayload(node, depth = 0) {
if (depth > 8 || node === null || node === undefined) return null;
if (typeof node === 'string') {
if (node.includes('.glb') || node.includes('misc/cdn-models')) return node;
return null;
}
if (Array.isArray(node)) {
for (const item of node) {
const found = findModelUrlInPayload(item, depth + 1);
if (found) return found;
}
return null;
}
if (typeof node === 'object') {
for (const [key, value] of Object.entries(node)) {
if (typeof value === 'string' && key.toLowerCase().includes('model') && value.includes('http')) {
return value;
}
const found = findModelUrlInPayload(value, depth + 1);
if (found) return found;
}
}
return null;
}
function looksLikeModelUrl(url) {
if (!url || typeof url !== 'string') return false;
return url.includes('.glb') || url.includes('misc/cdn-models') || url.includes('/models/');
}
async function processTaskPayload(taskId, payload, source) {
if (!taskId || !payload) return;
lastSeenTaskId = taskId;
const result = payload.result || payload;
const name = result?.name || result?.args?.draft?.prompt || 'model';
const modelUrl = findModelUrlInPayload(result);
if (!modelUrl) return;
modelUrlByTask.set(taskId, modelUrl);
taskId = normalizeTaskId(taskId) || taskId;
const size = await fetchSizeText(modelUrl);
saveModel(taskId, { url: modelUrl, name, size });
log(`task metadata captured from ${source}:`, taskId, modelUrl);
injectButtons(taskId, getModel(taskId));
publishDebugState();
}
function installTaskInterceptors() {
const taskUrlRegex = /api\.meshy\.ai\/web\/v2\/tasks\/([a-f0-9-]+)/i;
const originalFetch = window.fetch;
window.fetch = async function (...args) {
const response = await originalFetch.apply(this, args);
const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
const match = url && url.match(taskUrlRegex);
if (match) {
const taskId = match[1];
response.clone().json()
.then((payload) => processTaskPayload(taskId, payload, 'fetch'))
.catch((error) => log('fetch payload parse failed:', error.message));
}
if (looksLikeModelUrl(url)) {
response.clone().arrayBuffer()
.then((buffer) => {
if (!isGLBBuffer(buffer)) return;
const taskId = normalizeTaskId(null);
const model = taskId ? getModel(taskId) : null;
cacheGlbBuffer(taskId, model?.name || 'model', buffer, 'fetch-model');
})
.catch(() => {});
}
return response;
};
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this.__meshyUrl = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
this.addEventListener('load', () => {
const url = this.__meshyUrl;
const match = url && url.match(taskUrlRegex);
if (!match) return;
try {
const payload = JSON.parse(this.responseText);
processTaskPayload(match[1], payload, 'xhr');
} catch (error) {
log('xhr payload parse failed:', error.message);
}
});
return originalSend.apply(this, arguments);
};
}
function installGlbCaptureHooks() {
function tryCapture(raw, source, taskHint = null) {
const buffer = arrayBufferFromUnknown(raw);
if (!buffer || !isGLBBuffer(buffer)) return;
const taskId = normalizeTaskId(taskHint);
const model = taskId ? getModel(taskId) : null;
cacheGlbBuffer(taskId, model?.name || 'model', buffer, source);
}
function walkAndCapture(value, source, visited = new WeakSet(), depth = 0) {
if (depth > 6 || value === null || value === undefined) return false;
const directBuffer = arrayBufferFromUnknown(value);
if (directBuffer && isGLBBuffer(directBuffer)) {
tryCapture(directBuffer, source);
return true;
}
if (value instanceof Blob) {
value.arrayBuffer()
.then((buffer) => tryCapture(buffer, `${source}-blob`))
.catch(() => {});
return false;
}
if (typeof value !== 'object') return false;
if (visited.has(value)) return false;
visited.add(value);
if (Array.isArray(value)) {
for (const item of value) {
if (walkAndCapture(item, source, visited, depth + 1)) return true;
}
return false;
}
for (const child of Object.values(value)) {
if (walkAndCapture(child, source, visited, depth + 1)) return true;
}
return false;
}
const originalWorker = window.Worker;
if (typeof originalWorker === 'function') {
window.Worker = function (...args) {
const worker = new originalWorker(...args);
const spy = (event) => {
const data = event?.data;
walkAndCapture(data, 'worker-message');
};
const add = worker.addEventListener;
worker.addEventListener = function (type, listener, options) {
if (type === 'message' && typeof listener === 'function') {
const wrapped = function (event) {
try { spy(event); } catch {}
return listener.call(this, event);
};
return add.call(this, type, wrapped, options);
}
return add.call(this, type, listener, options);
};
try {
const proto = Object.getPrototypeOf(worker);
const desc = Object.getOwnPropertyDescriptor(proto, 'onmessage');
if (desc?.set) {
Object.defineProperty(worker, 'onmessage', {
configurable: true,
get() {
return desc.get ? desc.get.call(worker) : undefined;
},
set(fn) {
if (typeof fn !== 'function') {
desc.set.call(worker, fn);
return;
}
desc.set.call(worker, function (event) {
try { spy(event); } catch {}
return fn.call(this, event);
});
}
});
}
} catch (error) {
log('worker onmessage hook failed:', error.message);
}
return worker;
};
window.Worker.prototype = originalWorker.prototype;
Object.defineProperty(window.Worker, Symbol.hasInstance, {
value(instance) { return instance instanceof originalWorker; }
});
}
const originalCreateObjectURL = URL.createObjectURL.bind(URL);
URL.createObjectURL = function (blob) {
const url = originalCreateObjectURL(blob);
if (blob instanceof Blob && blob.size > 1024) {
blob.arrayBuffer()
.then((buffer) => {
tryCapture(buffer, 'createObjectURL');
})
.catch(() => {});
}
return url;
};
}
function checkCurrentPageModel() {
const taskId = normalizeTaskId(null);
if (!taskId) return;
injectButtons(taskId, getModel(taskId));
}
function installSpaUrlWatcher() {
let lastUrl = window.location.href;
new MutationObserver(() => {
const nextUrl = window.location.href;
if (nextUrl !== lastUrl) {
lastUrl = nextUrl;
setTimeout(checkCurrentPageModel, 300);
}
}).observe(document, { subtree: true, childList: true });
}
function init() {
installTaskInterceptors();
installGlbCaptureHooks();
installSpaUrlWatcher();
setTimeout(checkCurrentPageModel, 1000);
log('userscript initialized');
publishDebugState();
}
init();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment