Edits history of script submission #22298 for ' Read PDF Form (nextcloud)'

  • bun
    import axios from "axios";
    
    type FieldValue = string | boolean | null;
    
    type FieldKind = "text" | "checkbox" | "other";
    
    function trimStr(v: unknown): string {
      return String(v ?? "").trim();
    }
    
    /** True if the field has a value that counts as “filled” for PDF-required validation. */
    function isFilled(value: FieldValue, kind: FieldKind): boolean {
      if (kind === "checkbox") return value === true || value === false;
      if (kind === "text") {
        return typeof value === "string" && value.trim().length > 0;
      }
      return value != null && String(value).trim().length > 0;
    }
    
    type WidgetAnnotation = {
      fieldName?: string;
      fieldValue?: string | null;
      fieldType?: string;
      checkBox?: boolean;
      required?: boolean;
      fieldFlags?: number;
    };
    
    const FIELD_FLAG_REQUIRED = 2;
    
    function isAnnotationRequired(a: WidgetAnnotation): boolean {
      if (typeof a.required === "boolean") return a.required;
      const ff = a.fieldFlags;
      return typeof ff === "number" && (ff & FIELD_FLAG_REQUIRED) !== 0;
    }
    
    function kindFromAnnotation(a: WidgetAnnotation): FieldKind {
      const ft = a.fieldType;
      if (ft === "Tx") return "text";
      if (ft === "Btn") {
        if (a.checkBox) return "checkbox";
        return "other";
      }
      if (ft === "Ch") return "other";
      return "other";
    }
    
    function valueFromAnnotation(a: WidgetAnnotation): FieldValue {
      const ft = a.fieldType;
      const raw = a.fieldValue;
    
      if (ft === "Tx") return trimStr(raw ?? "");
    
      if (ft === "Btn" && a.checkBox) {
        const v = raw == null ? "" : String(raw);
        if (v === "" || v === "Off") return false;
        return true;
      }
      if (ft === "Ch") return trimStr(raw ?? "");
    
      return null;
    }
    
    /** pdf.js expects browser globals; Windmill workers do not provide `DOMMatrix`. */
    async function ensureDomMatrixPolyfill(): Promise<void> {
      if (typeof globalThis.DOMMatrix !== "undefined") return;
      const mod = await import("dommatrix");
      const DM = (mod as { default?: typeof globalThis.DOMMatrix }).default ?? (mod as { DOMMatrix: typeof globalThis.DOMMatrix }).DOMMatrix;
      if (typeof DM === "function") {
        Object.defineProperty(globalThis, "DOMMatrix", { value: DM, configurable: true });
      }
    }
    
    async function loadFormData(
      pdfBytes: Uint8Array,
      password: string,
    ): Promise<{
      values: Record<string, FieldValue>;
      kinds: Record<string, FieldKind>;
      available_fields: string[];
      required_fields: string[];
    }> {
      await ensureDomMatrixPolyfill();
      const { getDocument } = await import("pdfjs-dist/legacy/build/pdf.mjs");
    
      const loadingTask = getDocument({
        data: pdfBytes,
        password,
        disableRange: true,
        disableStream: true,
        useSystemFonts: true,
      });
    
      let pdf: { numPages: number; getPage: (n: number) => Promise<{ getAnnotations: () => Promise<unknown[]> }> };
      try {
        pdf = await loadingTask.promise;
      } catch (e: unknown) {
        const msg = e instanceof Error ? e.message : String(e);
        if (/password/i.test(msg)) {
          throw new Error(
            "PDF needs a password or a different one. Set `pdfPassword` or use an unencrypted PDF.",
          );
        }
        throw e;
      }
    
      const kinds: Record<string, FieldKind> = {};
      const values: Record<string, FieldValue> = {};
      const requiredByName: Record<string, boolean> = {};
    
      for (let p = 1; p <= pdf.numPages; p++) {
        const page = await pdf.getPage(p);
        const annotations = (await page.getAnnotations()) as WidgetAnnotation[];
    
        for (const a of annotations) {
          const name = a.fieldName?.trim();
          if (!name) continue;
    
          const kind = kindFromAnnotation(a);
          const v = valueFromAnnotation(a);
          kinds[name] = kind;
          values[name] = v;
    
          if (isAnnotationRequired(a)) requiredByName[name] = true;
          else if (requiredByName[name] === undefined) requiredByName[name] = false;
        }
      }
    
      const available_fields = Object.keys(values).sort();
      const required_fields = Object.keys(requiredByName)
        .filter((n) => requiredByName[n])
        .sort();
    
      return { values, kinds, available_fields, required_fields };
    }
    
    export async function main(
      nextcloud: RT.Nextcloud,
      pdfPath: string,
      pdfPassword: string | null = null,
    ): Promise<{
      values: Record<string, FieldValue>;
      available_fields: string[];
      required_fields: string[];
      filled_out: boolean;
    }> {
      const getRes = await axios.get(
        `${String(nextcloud.baseUrl || "").replace(/\/$/, "")}/remote.php/dav/files/${encodeURIComponent(nextcloud.userId)}/${pdfPath}`,
        {
          auth: {
            username: nextcloud.userId,
            password: nextcloud.token,
          },
          responseType: "arraybuffer",
        },
      );
    
      if (getRes.status !== 200) {
        throw new Error(`Failed to download PDF (HTTP ${getRes.status}) ${getRes.statusText}`);
      }
    
      const pdfBytes = new Uint8Array(getRes.data as ArrayBuffer);
      const { values, kinds, available_fields, required_fields } = await loadFormData(
        pdfBytes,
        pdfPassword ?? "",
      );
    
      const filled_out = required_fields.every((name) =>
        isFilled(values[name], kinds[name] ?? "other"),
      );
    
      return { values, available_fields, required_fields, filled_out };
    }
    

    Submitted by nextcloud 23 days ago

  • bun
    One script reply has been approved by the moderators
    Ap­pro­ved
    import axios from "axios";
    import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
    
    type FieldValue = string | boolean | null;
    
    type FieldKind = "text" | "checkbox" | "other";
    
    function trimStr(v: unknown): string {
      return String(v ?? "").trim();
    }
    
    /** True if the field has a value that counts as “filled” for PDF-required validation. */
    function isFilled(value: FieldValue, kind: FieldKind): boolean {
      if (kind === "checkbox") return value === true || value === false;
      if (kind === "text") {
        return typeof value === "string" && value.trim().length > 0;
      }
      return value != null && String(value).trim().length > 0;
    }
    
    type WidgetAnnotation = {
      fieldName?: string;
      fieldValue?: string | null;
      fieldType?: string;
      checkBox?: boolean;
      required?: boolean;
      fieldFlags?: number;
    };
    
    const FIELD_FLAG_REQUIRED = 2;
    
    function isAnnotationRequired(a: WidgetAnnotation): boolean {
      if (typeof a.required === "boolean") return a.required;
      const ff = a.fieldFlags;
      return typeof ff === "number" && (ff & FIELD_FLAG_REQUIRED) !== 0;
    }
    
    function kindFromAnnotation(a: WidgetAnnotation): FieldKind {
      const ft = a.fieldType;
      if (ft === "Tx") return "text";
      if (ft === "Btn") {
        if (a.checkBox) return "checkbox";
        return "other";
      }
      if (ft === "Ch") return "other";
      return "other";
    }
    
    function valueFromAnnotation(a: WidgetAnnotation): FieldValue {
      const ft = a.fieldType;
      const raw = a.fieldValue;
    
      if (ft === "Tx") return trimStr(raw ?? "");
    
      if (ft === "Btn" && a.checkBox) {
        const v = raw == null ? "" : String(raw);
        if (v === "" || v === "Off") return false;
        return true;
      }
      if (ft === "Ch") return trimStr(raw ?? "");
    
      return null;
    }
    
    async function loadFormData(
      pdfBytes: Uint8Array,
      password: string,
    ): Promise<{
      values: Record<string, FieldValue>;
      kinds: Record<string, FieldKind>;
      available_fields: string[];
      required_fields: string[];
    }> {
      const loadingTask = getDocument({
        data: pdfBytes,
        password,
        disableRange: true,
        disableStream: true,
        useSystemFonts: true,
      });
    
      let pdf: { numPages: number; getPage: (n: number) => Promise<{ getAnnotations: () => Promise<unknown[]> }> };
      try {
        pdf = await loadingTask.promise;
      } catch (e: unknown) {
        const msg = e instanceof Error ? e.message : String(e);
        if (/password/i.test(msg)) {
          throw new Error(
            "PDF needs a password or a different one. Set `pdfPassword` or use an unencrypted PDF.",
          );
        }
        throw e;
      }
    
      const kinds: Record<string, FieldKind> = {};
      const values: Record<string, FieldValue> = {};
      const requiredByName: Record<string, boolean> = {};
    
      for (let p = 1; p <= pdf.numPages; p++) {
        const page = await pdf.getPage(p);
        const annotations = (await page.getAnnotations()) as WidgetAnnotation[];
    
        for (const a of annotations) {
          const name = a.fieldName?.trim();
          if (!name) continue;
    
          const kind = kindFromAnnotation(a);
          const v = valueFromAnnotation(a);
          kinds[name] = kind;
          values[name] = v;
    
          if (isAnnotationRequired(a)) requiredByName[name] = true;
          else if (requiredByName[name] === undefined) requiredByName[name] = false;
        }
      }
    
      const available_fields = Object.keys(values).sort();
      const required_fields = Object.keys(requiredByName)
        .filter((n) => requiredByName[n])
        .sort();
    
      return { values, kinds, available_fields, required_fields };
    }
    
    export async function main(
      nextcloud: RT.Nextcloud,
      pdfPath: string,
      pdfPassword: string | null = null,
    ): Promise<{
      values: Record<string, FieldValue>;
      available_fields: string[];
      required_fields: string[];
      filled_out: boolean;
    }> {
      const getRes = await axios.get(
        `${String(nextcloud.baseUrl || "").replace(/\/$/, "")}/remote.php/dav/files/${encodeURIComponent(nextcloud.userId)}/${pdfPath}`,
        {
          auth: {
            username: nextcloud.userId,
            password: nextcloud.token,
          },
          responseType: "arraybuffer",
        },
      );
    
      if (getRes.status !== 200) {
        throw new Error(`Failed to download PDF (HTTP ${getRes.status}) ${getRes.statusText}`);
      }
    
      const pdfBytes = new Uint8Array(getRes.data as ArrayBuffer);
      const { values, kinds, available_fields, required_fields } = await loadFormData(
        pdfBytes,
        pdfPassword ?? "",
      );
    
      const filled_out = required_fields.every((name) =>
        isFilled(values[name], kinds[name] ?? "other"),
      );
    
      return { values, available_fields, required_fields, filled_out };
    }
    

    Submitted by nextcloud 23 days ago