Skip to content

Commit f09b5f7

Browse files
authored
Merge pull request #23 from sonicbaume/obf-read-manifest
Read OBZ manifest
2 parents b7e61fe + 068d894 commit f09b5f7

1 file changed

Lines changed: 47 additions & 13 deletions

File tree

src/processors/obfProcessor.ts

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ interface ObfButton {
5151
image_id?: string; // Reference to image in the images array
5252
}
5353

54+
interface ObfManifest {
55+
format?: string;
56+
root?: string;
57+
paths?: {
58+
boards?: { [key: string]: string };
59+
images?: { [key: string]: string };
60+
sounds?: { [key: string]: string };
61+
};
62+
}
63+
5464
/**
5565
* Map OBF hidden value to AAC standard visibility
5666
* OBF: true = hidden, false/undefined = visible
@@ -201,15 +211,14 @@ class ObfProcessor extends BaseProcessor {
201211
}
202212
}
203213

204-
private async processBoard(boardData: ObfBoard, _boardPath: string): Promise<AACPage> {
214+
private async processBoard(
215+
boardData: ObfBoard,
216+
_boardPath: string,
217+
isZipEntry: boolean
218+
): Promise<AACPage> {
205219
const sourceButtons = boardData.buttons || [];
206220

207221
// Calculate page ID first (used to make button IDs unique)
208-
const isZipEntry =
209-
_boardPath &&
210-
_boardPath.endsWith('.obf') &&
211-
!_boardPath.includes('/') &&
212-
!_boardPath.includes('\\');
213222
const pageId = isZipEntry
214223
? _boardPath // Zip entry - use filename to match navigation paths
215224
: boardData?.id
@@ -437,7 +446,7 @@ class ObfProcessor extends BaseProcessor {
437446
const boardData = tryParseObfJson(content);
438447
if (boardData) {
439448
console.log('[OBF] Detected .obf file, parsed as JSON');
440-
const page = await this.processBoard(boardData, filePathOrBuffer);
449+
const page = await this.processBoard(boardData, filePathOrBuffer, false);
441450
tree.addPage(page);
442451

443452
// Set metadata from root board
@@ -475,7 +484,7 @@ class ObfProcessor extends BaseProcessor {
475484
const asJson = tryParseObfJson(filePathOrBuffer);
476485
if (!asJson) throw new Error('Invalid OBF content: not JSON and not ZIP');
477486
console.log('[OBF] Detected buffer/string as OBF JSON');
478-
const page = await this.processBoard(asJson, '[bufferOrString]');
487+
const page = await this.processBoard(asJson, '[bufferOrString]', false);
479488
tree.addPage(page);
480489

481490
// Set metadata from root board
@@ -508,18 +517,43 @@ class ObfProcessor extends BaseProcessor {
508517

509518
console.log('[OBF] Detected zip archive, extracting .obf files');
510519

511-
// Collect all .obf entries
512-
const obfEntries = this.zipFile
513-
.listFiles()
514-
.filter((name) => name.toLowerCase().endsWith('.obf'));
520+
// List manifest and OBF files
521+
const filesInZip = this.zipFile.listFiles();
522+
const manifestFile = filesInZip.filter((name) => name.toLowerCase() === 'manifest.json');
523+
let obfEntries = filesInZip.filter((name) => name.toLowerCase().endsWith('.obf'));
524+
525+
// Attempt to read manifest
526+
if (manifestFile && manifestFile.length === 1) {
527+
try {
528+
const content = await this.zipFile.readFile(manifestFile[0]);
529+
const data = decodeText(content);
530+
const str = typeof data === 'string' ? data : readTextFromInput(data);
531+
if (!str.trim()) throw new Error('Manifest object missing');
532+
const manifestObject = JSON.parse(str) as ObfManifest;
533+
if (!manifestObject) throw new Error('Manifest object is empty');
534+
535+
// Replace OBF file list
536+
if (manifestObject.paths && manifestObject.paths.boards) {
537+
obfEntries = Object.values(manifestObject.paths.boards);
538+
}
539+
540+
// Move root board to top of list
541+
if (manifestObject.root) {
542+
obfEntries = obfEntries.filter((item) => item !== manifestObject.root);
543+
obfEntries.unshift(manifestObject.root);
544+
}
545+
} catch (err) {
546+
console.warn('[OBF] Error processing mainfest', err);
547+
}
548+
}
515549

516550
// Process each .obf entry
517551
for (const entryName of obfEntries) {
518552
try {
519553
const content = await this.zipFile.readFile(entryName);
520554
const boardData = tryParseObfJson(decodeText(content));
521555
if (boardData) {
522-
const page = await this.processBoard(boardData, entryName);
556+
const page = await this.processBoard(boardData, entryName, true);
523557
tree.addPage(page);
524558

525559
// Set metadata if not already set (use first board as reference)

0 commit comments

Comments
 (0)