0

Download Attachment

by
Published today

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 5 hours 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