diff --git a/extensions/google-meet/package.json b/extensions/google-meet/package.json index 52544b4fbc1c..4c3b0ede9763 100644 --- a/extensions/google-meet/package.json +++ b/extensions/google-meet/package.json @@ -8,6 +8,7 @@ }, "type": "module", "dependencies": { + "jszip": "3.10.1", "pretty-ms": "9.3.0", "typebox": "1.3.3" }, diff --git a/extensions/google-meet/src/cli.test.ts b/extensions/google-meet/src/cli.test.ts index d8097255b081..369ce0e48b72 100644 --- a/extensions/google-meet/src/cli.test.ts +++ b/extensions/google-meet/src/cli.test.ts @@ -3,6 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { Command } from "commander"; +import JSZip from "jszip"; import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime"; import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; import { registerGoogleMeetCli, testing } from "./cli.js"; @@ -674,7 +675,8 @@ describe("google-meet CLI", () => { expectFields(firstRecord(firstRecord(artifacts.artifacts).transcripts), { documentText: "Transcript document body.", }); - expect(readFileSync(`${tempDir}.zip`).subarray(0, 4).toString("hex")).toBe("504b0304"); + const zip = await JSZip.loadAsync(readFileSync(`${tempDir}.zip`)); + expect(await zip.file("summary.md")?.async("string")).toContain("# Google Meet Artifacts"); } finally { stdout.restore(); rmSync(tempDir, { recursive: true, force: true }); diff --git a/extensions/google-meet/src/cli.ts b/extensions/google-meet/src/cli.ts index 40ec2773c467..ab875bcedbb5 100644 --- a/extensions/google-meet/src/cli.ts +++ b/extensions/google-meet/src/cli.ts @@ -3,8 +3,8 @@ import path from "node:path"; import { createInterface } from "node:readline/promises"; import { format } from "node:util"; import type { Command } from "commander"; +import JSZip from "jszip"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; -import { expectDefined } from "openclaw/plugin-sdk/expect-runtime"; import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; import { clampTimerTimeoutMs, @@ -1302,92 +1302,6 @@ function defaultExportDirectory(): string { return `google-meet-export-${new Date().toISOString().replace(/[:.]/g, "-")}`; } -const CRC32_TABLE = new Uint32Array( - Array.from({ length: 256 }, (_, index) => { - let value = index; - for (let bit = 0; bit < 8; bit += 1) { - value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; - } - return value >>> 0; - }), -); - -function crc32(buffer: Buffer): number { - let value = 0xffffffff; - for (const byte of buffer) { - const tableValue = expectDefined( - CRC32_TABLE.at((value ^ byte) & 0xff), - "CRC32 lookup table entry", - ); - value = tableValue ^ (value >>> 8); - } - return (value ^ 0xffffffff) >>> 0; -} - -function dosDateTime(date = new Date()): { date: number; time: number } { - return { - time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2), - date: ((date.getFullYear() - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(), - }; -} - -function buildZipArchive(files: Array<{ name: string; content: string }>): Buffer { - const localParts: Buffer[] = []; - const centralParts: Buffer[] = []; - let offset = 0; - const stamp = dosDateTime(); - for (const file of files) { - const name = Buffer.from(file.name, "utf8"); - const content = Buffer.from(file.content, "utf8"); - const checksum = crc32(content); - const local = Buffer.alloc(30); - local.writeUInt32LE(0x04034b50, 0); - local.writeUInt16LE(20, 4); - local.writeUInt16LE(0, 6); - local.writeUInt16LE(0, 8); - local.writeUInt16LE(stamp.time, 10); - local.writeUInt16LE(stamp.date, 12); - local.writeUInt32LE(checksum, 14); - local.writeUInt32LE(content.length, 18); - local.writeUInt32LE(content.length, 22); - local.writeUInt16LE(name.length, 26); - local.writeUInt16LE(0, 28); - localParts.push(local, name, content); - - const central = Buffer.alloc(46); - central.writeUInt32LE(0x02014b50, 0); - central.writeUInt16LE(20, 4); - central.writeUInt16LE(20, 6); - central.writeUInt16LE(0, 8); - central.writeUInt16LE(0, 10); - central.writeUInt16LE(stamp.time, 12); - central.writeUInt16LE(stamp.date, 14); - central.writeUInt32LE(checksum, 16); - central.writeUInt32LE(content.length, 20); - central.writeUInt32LE(content.length, 24); - central.writeUInt16LE(name.length, 28); - central.writeUInt16LE(0, 30); - central.writeUInt16LE(0, 32); - central.writeUInt16LE(0, 34); - central.writeUInt16LE(0, 36); - central.writeUInt32LE(0, 38); - central.writeUInt32LE(offset, 42); - centralParts.push(central, name); - offset += local.length + name.length + content.length; - } - const centralDirectory = Buffer.concat(centralParts); - const end = Buffer.alloc(22); - end.writeUInt32LE(0x06054b50, 0); - end.writeUInt16LE(0, 4); - end.writeUInt16LE(0, 6); - end.writeUInt16LE(files.length, 8); - end.writeUInt16LE(files.length, 10); - end.writeUInt32LE(centralDirectory.length, 12); - end.writeUInt32LE(offset, 16); - end.writeUInt16LE(0, 20); - return Buffer.concat([...localParts, centralDirectory, end]); -} - export async function writeMeetExportBundle(params: { outputDir?: string; artifacts: GoogleMeetArtifactsResult; @@ -1435,7 +1349,11 @@ export async function writeMeetExportBundle(params: { files: files.map((file) => path.join(outputDir, file.name)), }; if (zipFile) { - await writeFile(zipFile, buildZipArchive(files)); + const zip = new JSZip(); + for (const file of files) { + zip.file(file.name, file.content); + } + await writeFile(zipFile, await zip.generateAsync({ type: "nodebuffer" })); result.zipFile = zipFile; } return result; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c6f01ab1660..6f4e6f703f8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -853,6 +853,9 @@ importers: extensions/google-meet: dependencies: + jszip: + specifier: 3.10.1 + version: 3.10.1 pretty-ms: specifier: 9.3.0 version: 9.3.0