0

Download Attachment

by
Published 20 days ago

Save a Gmail attachment to Windmill object storage and return its S3 reference. Requires gmail.readonly or gmail.modify on the connection.

Script gmail Verified

The script

Submitted by hugo989 Bun
Verified 21 days ago
1
import * as wmill from "windmill-client"
2
import { S3Object } from "windmill-client"
3

4
type Part = {
5
  partId: string
6
  mimeType: string
7
  filename?: string
8
  body?: { attachmentId?: string; data?: string; size?: number }
9
  parts?: Part[]
10
}
11

12
async function getAttachmentParts(auth: RT.Gmail, message_id: string) {
13
  const response = await fetch(
14
    `https://gmail.googleapis.com/gmail/v1/users/me/messages/${message_id}?format=full`,
15
    {
16
      headers: {
17
        Authorization: `Bearer ${auth.token}`,
18
        Accept: "application/json",
19
      },
20
    }
21
  )
22
  if (!response.ok) {
23
    throw new Error(`${response.status} ${await response.text()}`)
24
  }
25
  const { payload } = (await response.json()) as { payload: Part }
26
  const found: Part[] = []
27
  const walk = (p: Part) => {
28
    if (p.filename) found.push(p)
29
    p.parts?.forEach(walk)
30
  }
31
  walk(payload)
32
  return found
33
}
34

35
export type DynSelect_part_id = string
36

37
// Dropdown of the message's attachments (by MIME part id, which stays stable across fetches; attachmentId does not).
38
export async function part_id(auth: RT.Gmail, message_id: string) {
39
  return (await getAttachmentParts(auth, message_id)).map((p) => ({
40
    value: p.partId,
41
    label: `${p.filename} (${p.mimeType}, ${p.body?.size ?? 0} bytes)`,
42
  }))
43
}
44

45
/**
46
 * Download Attachment
47
 * Save one attachment of a Gmail message to Windmill object storage and return its S3 reference. `part_id` is the attachment's MIME `partId` (as in Get Message's `payload.parts`); pick it from the dropdown. Without `destination`, a key is generated. Requires gmail.readonly or gmail.modify on the connection.
48
 */
49
export async function main(
50
  auth: RT.Gmail,
51
  message_id: string,
52
  part_id: DynSelect_part_id,
53
  destination: S3Object | undefined
54
) {
55
  const part = (await getAttachmentParts(auth, message_id)).find(
56
    (p) => p.partId === part_id
57
  )
58
  if (!part) throw new Error(`No attachment with partId ${part_id}`)
59

60
  let data = part.body?.data
61
  if (!data) {
62
    const response = await fetch(
63
      `https://gmail.googleapis.com/gmail/v1/users/me/messages/${message_id}/attachments/${part.body?.attachmentId}`,
64
      {
65
        headers: {
66
          Authorization: `Bearer ${auth.token}`,
67
          Accept: "application/json",
68
        },
69
      }
70
    )
71
    if (!response.ok) {
72
      throw new Error(`${response.status} ${await response.text()}`)
73
    }
74
    data = ((await response.json()) as { data: string }).data
75
  }
76

77
  const file = await wmill.writeS3File(
78
    destination || undefined,
79
    new Blob([Buffer.from(data, "base64url")]),
80
    undefined,
81
    part.mimeType
82
  )
83
  return { ...file, filename: part.filename, mime_type: part.mimeType }
84
}
85