ai-chatbot/scripts/flow-board/inspect-xmind-package.py

42 lines
2.0 KiB
Python

"""Read an XMind app bundle's version and sample files without executing its code.
Usage: python3 scripts/flow-board/inspect-xmind-package.py /Volumes/Xmind/Xmind.app --samples-dir /tmp/xmind-native-samples
"""
import argparse
import hashlib
import io
import json
from pathlib import Path
import plistlib
import struct
import zipfile
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("app", type=Path)
parser.add_argument("--samples-dir", type=Path)
args = parser.parse_args()
info = plistlib.loads((args.app / "Contents/Info.plist").read_bytes())
report = {"version": info["CFBundleShortVersionString"], "build": info["CFBundleVersion"], "bundleId": info["CFBundleIdentifier"], "samples": []}
with (args.app / "Contents/Resources/app.asar").open("rb") as archive:
_, header_size, _, string_size = struct.unpack("<4I", archive.read(16))
header = json.loads(archive.read(string_size))
def walk(directory, prefix=""):
for name, item in directory.get("files", {}).items():
path = prefix + "/" + name
if "files" in item:
yield from walk(item, path)
elif name.endswith(".xmind"):
yield path, item
for name, item in walk(header):
archive.seek(8 + header_size + int(item["offset"]))
payload = archive.read(item["size"])
sample = {"path": name, "bytes": len(payload), "sha256": hashlib.sha256(payload).hexdigest()}
with zipfile.ZipFile(io.BytesIO(payload)) as workbook:
if "content.json" in workbook.namelist():
sheets = json.loads(workbook.read("content.json"))
sample["structures"] = [sheet["rootTopic"].get("structureClass") for sheet in sheets]
report["samples"].append(sample)
if args.samples_dir and "/basic/" in name:
args.samples_dir.mkdir(parents=True, exist_ok=True)
(args.samples_dir / Path(name).name).write_bytes(payload)
print(json.dumps(report, ensure_ascii=False, indent=2))