Skip to content
← Public packages

@danj/hey-email

HEY.com email for Kody: search threads, read messages, download PDF attachments, and write drafts. Sending is barred until you allow it.

src/search.js

61 lines · 2.3 KB · JavaScript
Raw
import { heyFetch } from './client.js'

const PAGE_SIZE = 10
const OWN_KEYS = new Set(['q', 'query', 'from', 'date', 'in', 'limit', 'page'])

function searchParams(input, page) {
  const params = new URLSearchParams()
  const parts = []
  const q = input.q ?? input.query
  if (q) parts.push(String(q))
  if (input.from) parts.push(`from:${String(input.from)}`)
  if (parts.length) params.set('q', parts.join(' '))
  if (input.date) params.set('date', String(input.date))
  if (input.in) params.set('in', String(input.in))
  for (const [k, v] of Object.entries(input)) {
    if (OWN_KEYS.has(k)) continue
    if (v == null || v === '') continue
    params.set(k, String(v))
  }
  // Official hey-sdk AdvancedSearch: page is 1-based and omitted on page 1.
  if (page > 1) params.set('page', String(page))
  return params
}

function pageCount(input) {
  const limit = input.limit == null || input.limit === '' ? null : Number(input.limit)
  if (limit > PAGE_SIZE) return Math.ceil(limit / PAGE_SIZE)
  return 1
}

/**
 * Search HEY mail via advanced_search.
 * Use when finding purchase/shipping/sale threads by keyword or sender.
 * Sender filters should be passed as from: "example.com" (embedded in q as from:domain).
 *
 * `page` is the 1-based results page the official SDK sends. Zero and one both
 * ask for the first page, which omits the query param. `limit` past 10 fetches
 * further pages (`page=2`, …) until that many matches or a short page.
 *
 * @param {{ q?: string, query?: string, from?: string, date?: string, in?: string, page?: number|string, limit?: number|string }} [input]
 * @returns {Promise<{ matches: unknown[], raw: unknown }>}
 *
 * @example
 * import search from 'kody:@danj/hey-email/search'
 * const { matches } = await search({ from: 'example.com', date: 'last_30_days', limit: 20 })
 */
export default async function search(input = {}) {
  const startPage = Number(input.page) > 1 ? Number(input.page) : 1
  const pages = pageCount(input)
  const matches = []
  let raw
  for (let i = 0; i < pages; i++) {
    const page = startPage + i
    const qs = searchParams(input, page).toString()
    raw = await heyFetch(`/advanced_search.json${qs ? `?${qs}` : ''}`)
    const batch = raw?.matches || []
    matches.push(...batch)
    if (batch.length < PAGE_SIZE) break
  }
  return { matches, raw }
}