fix(grabcraft): support external JS files for voxel data extraction
All checks were successful
Deploy to Docker / deploy (push) Successful in 13s
All checks were successful
Deploy to Docker / deploy (push) Successful in 13s
GrabCraft moved myRenderObject data from inline scripts to external JS files (/js/RenderObject/myRenderObject_XXXX.js). This adds extraction of the external script URL, fetching and parsing the nested JSON object, with fallback to the original inline extraction for legacy pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -125,7 +125,21 @@ export async function fetchBlueprint(url) {
|
||||
: 'Unknown Blueprint';
|
||||
|
||||
// Extract render object (3D voxel data)
|
||||
const voxels = extractRenderObject(html);
|
||||
// Try external JS file first (new GrabCraft format), then fall back to inline extraction
|
||||
let voxels = [];
|
||||
const externalScript = extractExternalScriptUrl(html);
|
||||
if (externalScript) {
|
||||
try {
|
||||
log(TAG, `Fetching external voxel data: ${externalScript.url}`);
|
||||
const jsContent = await fetchPage(externalScript.url);
|
||||
voxels = parseExternalRenderObject(jsContent);
|
||||
} catch (err) {
|
||||
logError(TAG, `Failed to fetch external JS: ${err.message}`);
|
||||
}
|
||||
}
|
||||
if (voxels.length === 0) {
|
||||
voxels = extractRenderObject(html); // fallback for legacy pages
|
||||
}
|
||||
|
||||
// Extract materials list
|
||||
const materials = extractMaterials(html);
|
||||
@@ -302,6 +316,76 @@ function extractRenderObject(html) {
|
||||
return voxels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract URL of external myRenderObject JS file from the page HTML.
|
||||
* GrabCraft now loads voxel data from e.g. /js/RenderObject/myRenderObject_1234.js
|
||||
* @param {string} html
|
||||
* @returns {{ url: string, blueprintId: string } | null}
|
||||
*/
|
||||
function extractExternalScriptUrl(html) {
|
||||
const regex = /<script[^>]+src=["']((?:https?:\/\/[^"']*)?\/js\/RenderObject\/myRenderObject_(\d+)\.js)["']/i;
|
||||
const match = html.match(regex);
|
||||
if (!match) return null;
|
||||
|
||||
let url = match[1];
|
||||
if (url.startsWith('/')) {
|
||||
url = GRABCRAFT_BASE + url;
|
||||
}
|
||||
return { url, blueprintId: match[2] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a nested myRenderObject from an external JS file.
|
||||
* Expected format: var myRenderObject = { y: { x: { z: { mat_id, hex, name, ... } } } };
|
||||
* @param {string} jsContent - Raw JS file content
|
||||
* @returns {Array<{ x: number, y: number, z: number, matId: string, hex: string|null, matName: string|null }>}
|
||||
*/
|
||||
function parseExternalRenderObject(jsContent) {
|
||||
const voxels = [];
|
||||
|
||||
// Strip "var myRenderObject = " prefix and trailing ";"
|
||||
const stripped = jsContent
|
||||
.replace(/^\s*var\s+myRenderObject\s*=\s*/, '')
|
||||
.replace(/;\s*$/, '')
|
||||
.trim();
|
||||
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(stripped);
|
||||
} catch {
|
||||
logError(TAG, 'Failed to JSON.parse external myRenderObject');
|
||||
return voxels;
|
||||
}
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) return voxels;
|
||||
|
||||
for (const [yKey, yVal] of Object.entries(obj)) {
|
||||
const y = parseInt(yKey, 10);
|
||||
if (isNaN(y) || typeof yVal !== 'object' || yVal === null) continue;
|
||||
|
||||
for (const [xKey, xVal] of Object.entries(yVal)) {
|
||||
const x = parseInt(xKey, 10);
|
||||
if (isNaN(x) || typeof xVal !== 'object' || xVal === null) continue;
|
||||
|
||||
for (const [zKey, entry] of Object.entries(xVal)) {
|
||||
const z = parseInt(zKey, 10);
|
||||
if (isNaN(z) || typeof entry !== 'object' || entry === null) continue;
|
||||
|
||||
voxels.push({
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
matId: String(entry.mat_id || '0'),
|
||||
hex: entry.hex || null,
|
||||
matName: entry.name || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return voxels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract materials list from Highcharts series data.
|
||||
* GrabCraft pages include chart data like:
|
||||
@@ -351,10 +435,13 @@ function extractMaterials(html) {
|
||||
* @returns {Promise<string>} HTML content
|
||||
*/
|
||||
async function fetchPage(url) {
|
||||
const isJs = url.endsWith('.js');
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept': isJs
|
||||
? 'application/javascript, */*;q=0.8'
|
||||
: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user