#!/usr/bin/env python3 """ Patch @met4citizen/talkinghead/dist/talkinghead.mjs to fix: "Cannot read properties of undefined (reading 'value')" Root cause: this.mtRandomized contains morph keys not in this.mtAvatar for the loaded GLB model. Line 2619: j = this.mtAvatar[i]; if (!j.needsUpdate) crashes. """ import os, sys, re PKG_DIR = os.path.join(os.getcwd(), 'node_modules', '@met4citizen', 'talkinghead') dist_files = ['dist/talkinghead.mjs', 'dist/talkinghead.js'] target_path = None for name in dist_files: p = os.path.join(PKG_DIR, name) if os.path.exists(p): target_path = p break if not target_path: print('ERROR: talkinghead file not found in ' + PKG_DIR) sys.exit(1) print('Patching: ' + target_path) with open(target_path, 'r') as f: code = f.read() if '__VT_PATCHED__' in code: print('Already patched, skipping.') sys.exit(0) # Fix 1: Null check for mtRandomized loop (line ~2619) old1 = "j = this.mtAvatar[i];\n if ( !j.needsUpdate ) {" new1 = "j = this.mtAvatar[i];\n if ( !j ) continue;\n __VT_PATCHED__=1;\n if ( !j.needsUpdate ) {" if old1 in code: code = code.replace(old1, new1) print('OK Fix1: added null check for mtRandomized loop') else: code = re.sub( r'(j = this\.mtAvatar\[i\];)\s*\n\s*if\s*\(\s*!j\.needsUpdate', r'\1\n if ( !j ) continue;\n __VT_PATCHED__=1;\n if ( !j.needsUpdate', code ) # Fix 2: Guard direct .value accesses on specific keys for key in ['bodyRotateY', 'eyeLookInLeft', 'eyeLookOutLeft', 'eyesLookDown', 'browDownLeft', 'browDownRight', 'eyeBlinkLeft', 'eyeBlinkRight', 'eyesLookUp', 'bodyRotateX', 'bodyRotateZ', 'headRotateX', 'headRotateY', 'headRotateZ']: full_access = 'this.mtAvatar[' + repr(key) + '].value' safe_access = "(this.mtAvatar[" + repr(key) + "] || {}).value || 0" code = code.replace('this.mtAvatar[' + repr(key) + '].value', safe_access) if '(this.mtAvatar[' + repr('bodyRotateY') + '] || {}).value' in code: print('OK Fix2: guarded direct .value accesses on known keys') # Fix 3: Wrap animate method in try/catch as safety net match = re.search( r'prototype\.animate\s*=\s*function\s*\(\s*t\s*\)\s*\{', code ) if match: brace_open = code.index('{', match.end() - 1) depth = 1 pos = brace_open + 1 while depth > 0 and pos < len(code): ch = code[pos] if ch == '{': depth += 1 elif ch == '}': depth -= 1 pos += 1 brace_close = pos - 1 indent = ' ' body = code[brace_open + 1:brace_close] if '__VT_CATCH__' not in body: new_body = ( indent + '__VT_CATCH__=1;\n' + indent + 'try {\n' + body + '\n' + indent + '} catch (e) {\n' + indent + indent + 'if (!(e && e.message && e.message.includes("Cannot read properties of undefined"))) {\n' + indent + indent + indent + 'console.warn("[patch_talkinghead] animate:", e?.message || e);\n' + indent + indent + indent + 'throw e;\n' + indent + indent + '}\n' + indent + '}\n' ) code = code[:brace_open + 1] + new_body + code[brace_close + 1:] print('OK Fix3: wrapped animate() in try/catch') else: print('WARN Fix3: could not wrap animate()') with open(target_path, 'w') as f: f.write(code) print('Done. Wrote ' + str(len(code)) + ' bytes')