Edits history of script submission #23029 for ' New Email (gmail)'

  • bunnative
    One script reply has been approved by the moderators
    Ap­pro­ved
    //native
    
    import * as wmill from "windmill-client"
    
    /**
     * New Email
     * Emits messages received since the last poll that match a Gmail search query (default `in:inbox`), oldest first, with headers, labels and snippet. The first run sets the watermark to now and emits nothing.
     */
    export async function main(auth: RT.Gmail, q: string = "in:inbox") {
      const lastChecked: number | undefined = await wmill.getState()
    
      // First run: set the watermark to now and don't emit a backlog.
      if (!lastChecked) {
        await wmill.setState(Date.now())
        return []
      }
    
      const base = "https://gmail.googleapis.com/gmail/v1/users/me/messages"
      const headers = {
        Authorization: `Bearer ${auth.token}`,
        Accept: "application/json",
      }
    
      // `after:` takes epoch seconds and is inclusive at the boundary second.
      const ids: string[] = []
      let pageToken: string | undefined
      do {
        const url = new URL(base)
        url.searchParams.append("q", `${q} after:${Math.floor(lastChecked / 1000)}`)
        url.searchParams.append("maxResults", "500")
        if (pageToken) url.searchParams.append("pageToken", pageToken)
        const response = await fetch(url, { headers })
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
        const page = (await response.json()) as {
          messages?: { id: string }[]
          nextPageToken?: string
        }
        ids.push(...(page.messages ?? []).map((m) => m.id))
        pageToken = page.nextPageToken
      } while (pageToken)
    
      // Fetch in batches: Gmail allows ~50 messages.get per second per user, and a
      // long gap between polls can return hundreds of ids.
      const messages: { internalDate: string }[] = []
      for (let i = 0; i < ids.length; i += 25) {
        const batch = await Promise.all(
          ids.slice(i, i + 25).map(async (id) => {
            const url = new URL(`${base}/${id}`)
            url.searchParams.append("format", "metadata")
            for (const h of ["From", "To", "Cc", "Subject", "Date", "Message-ID"])
              url.searchParams.append("metadataHeaders", h)
            const r = await fetch(url, { headers })
            if (!r.ok) throw new Error(`${r.status} ${await r.text()}`)
            return (await r.json()) as { internalDate: string }
          })
        )
        messages.push(...batch)
      }
    
      // Drop what the inclusive boundary second already emitted.
      const fresh = messages
        .filter((m) => Number(m.internalDate) > lastChecked)
        .sort((a, b) => Number(a.internalDate) - Number(b.internalDate))
    
      if (fresh.length > 0) {
        await wmill.setState(Number(fresh[fresh.length - 1].internalDate))
      }
    
      return fresh
    }
    

    Submitted by hugo989 4 hours ago