Edits history of script submission #23522 for ' Upload File (ms_onedrive)'

  • bun
    One script reply has been approved by the moderators
    Ap­pro­ved
    import * as wmill from "windmill-client"
    import { S3Object } from "windmill-client"
    
    const graph = "https://graph.microsoft.com/v1.0"
    
    function driveUrl(drive_id: string | undefined) {
      return drive_id != null && drive_id !== ""
        ? `${graph}/drives/${encodeURIComponent(drive_id)}`
        : `${graph}/me/drive`
    }
    
    // An item is addressed by its id, or by a path from the drive root starting with "/"
    function ref(item: string | undefined) {
      if (item == null || item === "" || item === "/") return "root"
      if (!item.startsWith("/")) return `items/${encodeURIComponent(item)}`
      const path = item
        .replace(/\/+$/, "")
        .split("/")
        .map(encodeURIComponent)
        .join("/")
      return `root:${path}:`
    }
    
    /**
     * Upload File
     * Upload a file from Windmill object storage to a folder in your OneDrive, or in another drive when `drive_id` is set. `folder` is an id, or a path from the drive root starting with "/" (e.g. `/Reports/2026`), the root when empty; `name` defaults to the object's file name. Files over 4 MB go through a resumable upload session in 10 MB chunks. Requires Files.ReadWrite (a connection default); a drive that is not your own needs Files.ReadWrite.All.
     */
    export async function main(
      auth: RT.MsOnedrive,
    
      file: S3Object,
      folder: string | undefined,
      name: string | undefined,
      conflict_behavior: "rename" | "replace" | "fail" = "rename",
      drive_id: string | undefined
    ) {
      const drive = driveUrl(drive_id)
      // ponytail: the whole file is held in memory; stream chunks from loadS3FileStream if multi-GB uploads matter
      const bytes = await wmill.loadS3File(file)
      if (!bytes) throw new Error(`Could not load ${file.s3}`)
    
      const fileName = encodeURIComponent(
        name != null && name !== "" ? name : file.s3.split("/").pop()!
      )
      const parent = ref(folder)
      const target = parent.startsWith("items/")
        ? `${drive}/${parent}:/${fileName}:`
        : `${drive}/root:${parent === "root" ? "" : parent.slice(5, -1)}/${fileName}:`
    
      if (bytes.length <= 4 * 1024 * 1024) {
        const response = await fetch(
          `${target}/[email protected]=${conflict_behavior}`,
          {
            method: "PUT",
            headers: {
              Authorization: `Bearer ${auth.token}`,
              "Content-Type": "application/octet-stream",
              Accept: "application/json",
            },
            body: bytes,
          }
        )
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
        return await response.json()
      }
    
      const session = await fetch(`${target}/createUploadSession`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${auth.token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        body: JSON.stringify({
          item: { "@microsoft.graph.conflictBehavior": conflict_behavior },
        }),
      })
      if (!session.ok) {
        throw new Error(`${session.status} ${await session.text()}`)
      }
      const { uploadUrl } = (await session.json()) as { uploadUrl: string }
    
      // Chunks must be a multiple of 320 KiB; the upload URL is pre-authenticated
      const chunk = 10 * 1024 * 1024
      for (let start = 0; ; start += chunk) {
        const end = Math.min(start + chunk, bytes.length)
        const response = await fetch(uploadUrl, {
          method: "PUT",
          headers: {
            "Content-Range": `bytes ${start}-${end - 1}/${bytes.length}`,
          },
          body: bytes.subarray(start, end),
        })
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
        if (end === bytes.length) return await response.json()
      }
    }
    

    Submitted by hugo989 5 hours ago