Skip to content

Repository files navigation

Intertel Conversa · Exporter

A local, read-only Playwright application that exports conversations from Intertel Conversa to Excel, CSV, JSON and JSONL - preserving each chat exactly as displayed.

Node TypeScript Playwright Vitest

How it works · Safety · Getting started · Commands · Speed · Output · Troubleshooting

This project is not affiliated with or endorsed by Intertel.


How it works

Intertel's application DOM is private and not known in advance, so nothing is hardcoded. You point the exporter at each region of the interface once, it saves stable selectors locally, and every later run reads the interface through those selectors.

Architecture at a glance

flowchart TB
  subgraph setup["One-time setup"]
    direction LR
    LOGIN["Manual login"] --> PROFILE[("Browser profile<br/>data/browser-profile")]
    CALIBRATE["Selector calibration"] --> SELECTORS[("Selectors<br/>config/selectors.json")]
  end

  subgraph runtime["Export runtime"]
    direction LR
    DISCOVER["Discover conversations"] --> QUEUE["Build work queue"]
    QUEUE --> WORKERS["Scrape in worker tabs"]
    CHECK["Check completeness"]
  end

  subgraph storage["Local persistence and output"]
    direction LR
    RAW[("Raw records<br/>data/raw")]
    CHECKPOINT[("Checkpoint<br/>data/checkpoints")]
    WRITERS["Output writers"]
    FILES[["Excel · CSV · JSON · JSONL<br/>output"]]
    WRITERS --> FILES
  end

  PROFILE --> DISCOVER
  SELECTORS --> DISCOVER
  WORKERS --> RAW
  WORKERS --> CHECKPOINT
  RAW --> CHECK
  CHECKPOINT --> CHECK
  CHECK --> WRITERS

  RAW --> VALIDATE["Validate later"]
  CHECKPOINT --> VALIDATE
  VALIDATE --> WRITERS
Loading

All persistent data stays local. Raw records and the checkpoint are the source of truth; generated files can be rebuilt with npm run validate.

Module ownership

Path Responsibility
src/cli.ts Commands, the export run loop and the parallel worker pool
src/browser/ Persistent Chromium profile, session, and the in-page panel
src/calibration/ Guided selector capture, channel detection, selector validation
src/scraper/ Discovery, history walking, DOM extraction, parsing, completeness
src/storage/ Atomic raw records and the resumable checkpoint
src/export/ Excel, CSV, JSON, JSONL writers and the live run progress model
src/utils/ Atomic file writes, hashing, retries, time and error classification

The export pipeline

Each queued conversation follows the same path. With --concurrency, multiple worker tabs run this path at the same time and share the serialized checkpoint.

flowchart TD
  START(["npm run export"]) --> PRE["Validate session and selectors"]
  PRE --> DISCOVER["Discover the complete conversation list"]
  DISCOVER --> PLAN["Apply resume, retry and limit options"]

  subgraph worker["Worker pool · one conversation per tab"]
    direction TB
    OPEN["Relocate and open conversation"] --> CHANNEL["Detect its channel"]
    CHANNEL --> WALK["Walk message history to the oldest edge"]
    WALK --> RESULT{"Outcome"}
    RESULT -- "complete" --> COMPLETE["Save complete record"]
    RESULT -- "partial" --> PARTIAL["Save partial record and diagnostic"]
    RESULT -- "failed" --> FAILED["Save failure diagnostic"]
  end

  PLAN --> OPEN
  COMPLETE --> PERSIST["Persist result and update checkpoint"]
  PARTIAL --> PERSIST
  FAILED --> PERSIST
  PERSIST --> DRAIN["Continue until the queue is empty and all workers finish"]
  DRAIN --> WRITE["Write Excel, CSV, JSON and JSONL"]
  WRITE --> VERDICT{"Completeness proven?"}
  VERDICT -- "yes" --> DONE(["Complete"])
  VERDICT -- "no" --> INCOMPLETE(["Incomplete · review diagnostics"])
Loading

One scroll cycle

Reading a conversation is where the time goes, so a whole cycle - scroll, wait for the panel to settle, read every row - happens in a single call into the page. Messages already captured come back as bare ids instead of their text, so a long chat stops re-sending what the scraper holds.

sequenceDiagram
  autonumber
  participant N as Node
  participant P as Page

  N->>P: advanceHistoryCycle(knownIds, quietMs)
  activate P
  P->>P: scroll up (jump to top, or one screen)
  P->>P: MutationObserver waits until the panel is quiet
  P->>P: read every visible row
  P-->>N: new rows + ids of known rows + scroll metrics + alerts
  deactivate P
  N->>N: build records for new rows only
  N->>N: merge into the ordered transcript
  Note over N,P: repeat until the top is reached or the guard trips
Loading

Safety boundary

The exporter uses your normal authenticated browser session and only reads the conversation UI. It does not send messages, change statuses, assign conversations, alter labels, archive data, or bypass authentication.

  • It exports whatever the calibrated conversation list contains, whichever channel that is. Each conversation's channel is detected and recorded on the record, but it is not a filter, so calibrate against the section you actually want to export. Facebook, Messenger, WhatsApp, Instagram, Telegram, SMS, email and web chat are recognised by name; anything else is exported just the same and recorded as unknown.
  • The only thing written into the page is the exporter's own guidance panel and temporary outlines around the element you are calibrating or the conversation being exported. Those styles are restored as soon as the step or the run moves on.
  • Conversation data stays on your machine and is never sent to a language or text-processing provider. Cookies and tokens are never printed or exported.

Getting started

Prerequisites - Node.js 20+, npm, and permission to access and export the client data.

npm install
npx playwright install chromium
Copy-Item .env.example .env

Review .env before running, then work through the four steps below.

flowchart LR
  A["1 · Log in manually"] --> B["2 · Calibrate selectors"]
  B --> C["3 · Test three conversations"]
  C --> D["4 · Run the full export"]
Loading

1. Sign in

npm run login

A dedicated headed Chromium profile opens at your INTERTEL_URL. Sign in manually in the browser - never type the password into the terminal. A Sign in to Intertel card appears in the corner and follows you across page reloads; when the authenticated application is visible, click Save session and continue. The reusable profile stays in data/browser-profile/, but Chromium password and autofill databases are removed whenever the exporter opens or closes.

SESSION_MAX_AGE_HOURS controls how long the authenticated browser session may be reused. Once that age is reached, the dedicated profile is cleared before Chromium opens and manual login is required. Leave the value empty to keep the session until Intertel expires it. This checkout uses six hours.

2. Calibrate

npm run calibrate

Calibration happens entirely inside Chromium. Each guided step draws a small diagram of the Intertel screen with the region you need outlined in orange and marked CLICK, plus written click/avoid instructions.

Control What it does
Keep selection Accepts the highlighted element and moves on
Discard and retry Picks the same step again
← Previous step Goes back one step
Skip this optional step Leaves an optional field uncalibrated
Unselect highlight Clears the dashed outlines so you can see the interface underneath
Clear / unselect Drops a saved selection entirely

The panel never blocks your work: drag it by its header, or collapse it to a single bar with (double-clicking the header does the same). Its position and collapsed state carry to the next step, and element picking keeps working while it is collapsed. It renders in an isolated shadow root, so Intertel's own styles cannot distort it.

One optional step asks for the Load more conversations control at the bottom of the list. Calibrating it lets discovery continue through every additional batch instead of stopping when that control appears. Older calibration files that predate this step are rejected deliberately, so an export cannot silently stop at the first batch.

Calibration saves local selectors to config/selectors.json and element screenshots to data/screenshots/calibration/. Both are Git-ignored: they can be account-specific or contain customer information. Stable attributes are preferred; generated classes are only a fallback.

3. Test with three conversations

npm run export:test

Runs headed, processes at most three conversations, and generates every output format. Review message order, chat text, timestamps, sender roles, attachments and the run summary before committing to a full run.

4. Export

npm run export

Discovery and every conversation are checkpointed continuously, and chat is atomically persisted while the history is being walked, so an interrupted run resumes with the same command.


Commands

Command Purpose
npm run login Open Chromium for a manual sign-in and store the local profile
npm run calibrate Capture and validate the Intertel selectors interactively
npm run export:test Headed run limited to three conversations
npm run export Full run, resuming from the checkpoint
npm run retry-failed Re-attempt only failed and partial conversations
npm run validate Re-check checkpoints, records and completeness; rewrite the outputs

Export options

Option Default Notes
--headed / --headless from .env Headed shows the live panel
--concurrency <n> EXPORT_CONCURRENCY Conversations scraped at once, one tab each; max 6
--limit <n> all Stop after n conversations
--resume on in npm run export Continue from the checkpoint
--force off Rescrape conversations already completed
--retry-failed off Only previously failed or partial conversations
--download-attachments off Save files under output/attachments/{conversation_id}/
--max-list-scrolls <n> MAX_LIST_SCROLLS Discovery guard
--max-message-scrolls <n> MAX_MESSAGE_SCROLLS Per-conversation history guard
--delay-between-conversations <ms> .env Pause between conversations
--output-directory <path> output/ Where the artefacts are written

Configuration

Variable Default Meaning
INTERTEL_URL https://app.intertelconversa.com/app Where the browser opens
BROWSER_HEADLESS false Default mode when no flag is passed
SESSION_MAX_AGE_HOURS 6 Hours before local login expires; empty never does
EXPORT_CONCURRENCY 1 Default tabs; 3 is a sensible everyday value
DELAY_AFTER_NAVIGATION_MS 400 Ceiling for a conversation to render after opening
DELAY_AFTER_SCROLL_MS 120 Quiet window the panel must hold before it is read
DELAY_BETWEEN_CONVERSATIONS_MS 50 Pause between conversations
MAX_LIST_SCROLLS 10000 Discovery scroll guard
MAX_MESSAGE_SCROLLS 300 History scroll guard per conversation
LOG_LEVEL info debug adds scroll-cycle detail
TIMEZONE America/Mexico_City Used to resolve zone-less timestamps

The DELAY_* values are settle ceilings, not fixed sleeps: the scraper waits for the page to go quiet and only falls back to these. Raise them if Intertel renders slowly and conversations come back partial.


Watching an export run

Headed exports show a live panel in the Chromium window:

  • a segmented meter and conversation count, readable at a glance
  • completed / partial / failed / skipped counters
  • Capturing - the messages being read out of the open conversation, newest last, colour-coded by speaker
  • the workbook line: which file was written, how many conversations and message rows it holds, and when it was last saved
  • a feed of the same redacted log lines that reach the terminal

On the page itself, the conversation being exported is outlined in the list and each message row washes blue as it is read, so you can watch the scrape move through the chat. Both marks are removed when the run finishes. With several tabs the panel narrates the tab you are looking at while the counters aggregate every worker. Headless runs skip the panel and log to the terminal only.


Speed

Nothing resolves a browser locator per field; a whole scroll cycle is one page call, the settle window starts the instant the scroll happens, and already-captured messages come back as bare ids. Opening a conversation matches its row by identity inside the page rather than reading the whole list. Panels that only append rows are walked by jumping straight to the end; panels that unmount rows as they scroll are detected on the next cycle and walked conservatively so nothing is skipped.

What remains is Intertel's own rendering time, and the only way to shorten that is to overlap it:

npm run export -- --resume --concurrency 3

Each worker takes the next conversation in its own tab of the same signed-in session. Measured on a 24-conversation fixture, with every message captured in each case:

Tabs Per conversation 285 conversations
1 0.92s ~4.4 min
3 0.34s ~1.6 min
4 0.24s ~1.1 min

Concurrency is off by default because more tabs means more load on Intertel and a higher chance of tripping its rate limiting, which pauses the run and costs the time back. Three is a sensible ceiling for everyday use; six is the maximum accepted.

Every extra tab must prove it is on the calibrated inbox before it is used. A tab that cannot is closed and the run continues with fewer - fewer workers is merely slower, whereas a tab on the wrong section would produce wrong data. Records are written per conversation and the checkpoint serialises its writes, so a parallel run resumes exactly like a serial one.


Output files

The default destination is output/.

File Contents
intertel-export.xlsx The workbook - see below
intertel-conversations.csv One row per conversation
intertel-messages.csv One row per message
intertel-raw.json Structured chat backup
intertel-rag.jsonl One retrieval-friendly record per conversation
intertel-errors.csv Failed, partial and warning records
intertel-run-summary.json Machine-readable counts and completeness result

The workbook opens on a Run Summary cover sheet - the completeness verdict, the figures, warnings and a guide to the other sheets - followed by:

  • Conversation View - a readable transcript where each conversation is a labelled band you can collapse, with the speaker colour-coded per message
  • Conversations, Messages, Errors - grids with frozen headers, filters, real date cells and colour-coded status columns

It stays on Calibri so emoji and keycap digits render, and colour marks state only: a header, a status cell, a failed row. Nothing is tinted for decoration.

Source records, checkpoints, screenshots and structured diagnostics stay under data/. Every customer-data directory is Git-ignored and nothing is uploaded automatically.


Completeness

File generation is not proof that every conversation was exported. The run reports complete only when all of these hold:

  • the conversation list reached a stable end rather than a scroll guard
  • every discovered eligible conversation was processed
  • none failed or remained partial
  • no message-history scroll guard was reached

Check intertel-run-summary.json, the Run Summary worksheet and intertel-errors.csv. A status of incomplete is intentional whenever the evidence is insufficient.


Troubleshooting

Symptom What to do
Expired session Run npm run login, sign in, retry
Missing or stale selectors Open a representative conversation and rerun npm run calibrate; UI changes commonly require recalibration
Run stops on "Open the conversation inbox" The calibrated regions are not on screen - open the desired section and one conversation, then click Re-check and start export
Empty output Confirm you started in the correct section, a conversation was open during calibration, and the list panel actually holds visible cards
Conversation list never finishes Raise MAX_LIST_SCROLLS, but inspect the summary - hitting the guard is always reported as incomplete
Long history comes back partial Raise MAX_MESSAGE_SCROLLS, then npm run retry-failed
Rate limits or an unstable UI Drop to --concurrency 1, use headed mode, and raise the DELAY_* ceilings
Excel shows ##### The column is narrower than its date - widen it; the exporter sizes columns from content

Development

npm run typecheck
npm run lint
npm test

The suite is deliberately small and aimed at silent data loss, the failure an exporter cannot afford. It covers the virtualized-panel guards, message-history completeness, the complete or incomplete verdict, message deduplication, every output format, and checkpoint resume under parallel writes. Some of it drives a local Playwright Chromium - no Intertel account and no customer data are involved.

About

A local, read-only Playwright application that exports conversations from Intertel Conversa to Excel, CSV, JSON and JSONL - preserving each chat exactly as displayed.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages