Skip to content

feat(cli): download voice notes, files, and images from chat messages - #27

Open
sinjab wants to merge 1 commit into
mingnz:mainfrom
sinjab:feat/media-file-image-downloads
Open

sinjab wants to merge 1 commit into
mingnz:mainfrom
sinjab:feat/media-file-image-downloads

Conversation

@sinjab

@sinjab sinjab commented Oct 10, 2026

Copy link
Copy Markdown

What

Adds media/file/image downloads to the CLI, and stops messages from silently dropping media-only messages.

  • teams media <chat> [id] β€” downloads the newest (or a given) voice note / media / image message. The playable/viewable object URL is fetched with the ic3 token; the output extension is derived from the response content type (webm/mp4/jpg/png).
  • teams file <chat> [match] β€” downloads file attachments by file-name substring or message id, riding the pre-authed tempauth link (botFileProperties.url, SharePoint objectUrl fallback). A 403 gets an explicit hint that tempauth links expire shortly after sending and the file needs re-sharing.
  • messages marker rows β€” media/file/image-only messages previously produced empty stripHtml content and were dropped from the output entirely (an image-only message was invisible in the conversation read). They now render [voice note 28s β€” media id …], [file: report.pdf β€” file id …], [image 800w β€” img id …], and captioned images get an inline [image …] marker appended to the text.
  • The extractors (extractMedia / extractImage / extractFile) are exported from formatting.ts and unit-tested against real message payloads.

Where the URLs come from

  • Voice notes/media: properties.cards JSON β†’ content.media[0].url (…/v1/objects/<ref>/views/audio), duration parsed from the PT..S string.
  • Images: the raw HTML <img src="…/v1/objects/<ref>/views/imgo"> inside the message content (with &amp; unescaped, width captured when present).
  • Files: properties.files JSON.

Validation

Tested against live Teams traffic: voice-note downloads come back byte-identical to the browser blob, and image downloads come back byte-identical to a manual object-URL fetch with the same bearer token. 14 new unit tests (106 total, all green); biome check clean; tsup build clean.

Notes

  • Media downloads hit the object host directly (not the chatsvc base URL) and therefore need the raw ic3 token β€” the PR uses the existing getToken("ic3") accessor.
  • Image marker rows include the width when the <img> tag carries one ([image 800w …], else ?).

- new `media <chat> [id]` command: downloads the newest (or a given)
  voice note / media / image message. Media URLs are fetched with the
  ic3 token; the output extension is derived from the content type
  (webm/mp4/jpg/png).
- new `file <chat> [match]` command: downloads file attachments by
  file-name substring or message id, riding the pre-authed tempauth
  link (with an explicit expired-link hint on 403).
- `messages` no longer drops media/file/image-only messages: they now
  render marker rows (`[voice note 28s β€” media id …]`, `[file: …]`,
  `[image 800w β€” img id …]`), and captioned images get an inline
  `[image …]` marker appended. Previously a photo-only message was
  invisible in `messages` output entirely.
- extractors (extractMedia / extractImage / extractFile) are exported
  and unit-tested against real message shapes.

Validated against live Teams traffic: voice-note downloads are
byte-identical to the browser blob, and image downloads are
byte-identical to a manual object-URL fetch.
@sinjab

sinjab commented Oct 15, 2026

Copy link
Copy Markdown
Author

Friendly ping β€” happy to rebase this onto current main if anything has drifted. I'm also sending a few follow-up PRs (#30–#32: --json output, HTML send, file attachments) built on this line of work; no rush, just letting you know they're coming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants