wacrawl-dashboard 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +220 -105
  2. package/apps/api/dist/demo/demoArchive.js +371 -0
  3. package/apps/api/dist/demo/demoCast.js +138 -0
  4. package/apps/api/dist/demo/demoMedia.js +108 -0
  5. package/apps/api/dist/index.js +109 -29
  6. package/apps/api/dist/lib/archiveSchema.js +57 -0
  7. package/apps/api/dist/lib/archiveSync.js +151 -0
  8. package/apps/api/dist/lib/chatKind.js +41 -0
  9. package/apps/api/dist/lib/displayName.js +20 -0
  10. package/apps/api/dist/lib/mediaFsPath.js +27 -0
  11. package/apps/api/dist/lib/percentile.js +23 -0
  12. package/apps/api/dist/lib/query.js +33 -7
  13. package/apps/api/dist/lib/rollupDb.js +253 -0
  14. package/apps/api/dist/lib/searchFilters.js +141 -0
  15. package/apps/api/dist/lib/selfIdentity.js +42 -0
  16. package/apps/api/dist/lib/statsCache.js +83 -0
  17. package/apps/api/dist/lib/timezone.js +102 -0
  18. package/apps/api/dist/lib/wordCloudUsefulStopWords.js +36 -3
  19. package/apps/api/dist/pathsResolve.js +17 -3
  20. package/apps/api/dist/routes/data.js +141 -77
  21. package/apps/api/dist/routes/settings.js +3 -0
  22. package/apps/api/dist/routes/stats/profiles.js +522 -0
  23. package/apps/api/dist/routes/stats/reports.js +555 -0
  24. package/apps/api/dist/routes/stats/shared.js +75 -0
  25. package/apps/api/dist/routes/stats.js +273 -323
  26. package/apps/api/dist/routes/sync.js +60 -0
  27. package/apps/api/dist/scripts/generateDemoArchive.js +24 -0
  28. package/apps/api/package.json +31 -0
  29. package/apps/web/dist/apple-touch-icon.png +0 -0
  30. package/apps/web/dist/assets/charts-BMGh5n-d.js +36 -0
  31. package/apps/web/dist/assets/icons-BdElrSaU.js +1 -0
  32. package/apps/web/dist/assets/index-Bb8Zj42D.css +1 -0
  33. package/apps/web/dist/assets/index-wrxENU1e.js +9 -0
  34. package/apps/web/dist/assets/react-D3RL6ob4.js +3 -0
  35. package/apps/web/dist/favicon-16x16.png +0 -0
  36. package/apps/web/dist/favicon-32x32.png +0 -0
  37. package/apps/web/dist/favicon-512x512.png +0 -0
  38. package/apps/web/dist/favicon.ico +0 -0
  39. package/apps/web/dist/favicon.svg +59 -0
  40. package/apps/web/dist/index.html +11 -2
  41. package/bin/wacrawl-dashboard.js +64 -15
  42. package/package.json +20 -4
  43. package/apps/web/dist/assets/index-CFh_Wz98.js +0 -43
  44. package/apps/web/dist/assets/index-DJiLhgJZ.css +0 -1
package/README.md CHANGED
@@ -1,149 +1,264 @@
1
- # WaCrawl Dashboard
1
+ # WaCrawl Dashboard 📊💬
2
2
 
3
- Local WhatsApp analytics dashboard for the SQLite archive generated by [wacrawl](https://github.com/steipete/wacrawl). A small monorepo runs a **read-only Express API** (`apps/api`) and a **React + Vite** UI (`apps/web`) that talks to it over CORS on localhost only.
3
+ > *Your WhatsApp history, finally looked at by someone other than you at 2 a.m.*
4
4
 
5
- ## One-line install
5
+ A local analytics dashboard for the SQLite archive produced by [wacrawl](https://github.com/steipete/wacrawl). It answers the questions you never asked out loud: who texts you first, who ghosts you, which group chat is really a monologue, and exactly how many times you've sent 😂 this year. (It's a lot. We've seen your data. Well, *we* haven't. That's the point.)
6
6
 
7
- ```bash
8
- npx wacrawl-dashboard@latest
9
- ```
10
-
11
- Opens the dashboard on `http://127.0.0.1:3001`. Requires Node 20+ and a WaCrawl archive at `~/.wacrawl/wacrawl.db`.
12
- Point it at a different archive:
13
-
14
- ```bash
15
- WACRAWL_DB=/path/to/wacrawl.db npx wacrawl-dashboard@latest
16
- ```
17
-
18
- ![WaCrawl Dashboard screenshot](docs/dashboard-2.png)
19
-
20
- ## Features
7
+ You can read more about it [here](https://greenido.dev/2026/05/21/unlock-whatsapp-data-with-local-analytics-dashboard/).
21
8
 
22
- ### Dashboard
9
+ Everything runs on your machine. The API binds to `127.0.0.1`, opens the archive **read-only**, and has no interest in phoning home. Your chats stay between you, your friends, and that one cousin who forwards chain messages.
23
10
 
24
- - **Overview cards** — total messages, chats, contacts, media files, and archive date span; each card can jump to Search, Chats, People, or Media as a shortcut.
25
- - **Time window** — period filter in the top bar (**Day / Week / Month / Year / All**) drives chart stats (default **Year**). The **activity heatmap** uses the current calendar year.
26
- - **Charts and lists** — message volume over time, top contacts (bar chart; click a contact to open **Chats** filtered to that JID), hour-of-day and day-of-week activity, monthly sent vs received vs ratio, media type breakdown (count and bytes), top media senders, response-time averages, busiest groups, messaging streaks (current and longest).
27
- - **Word cloud** — top terms with an **All words** vs **Useful words** (stopword-filtered) toggle; click any term to search your archive for it.
28
- - **Emoji analytics** — total and unique emoji counts, top emojis by **All / Sent / Received**, and a leaderboard of top emoji users with their favourite emoji.
29
- - **Conversation dynamics** — a dedicated section with relationship-level insights:
30
- - **Who starts conversations** — initiation ratio per contact with a visual split bar (sessions separated by 4h+ gaps).
31
- - **Conversation depth** — average messages per session, showing which chats go deep.
32
- - **Ghost score** — percentage of your messages that went unanswered within 24 hours, colour-coded by severity.
33
- - **Late-night texters** — who you talk to between midnight and 5 am, with hour-bucket breakdowns.
34
- - **Relationship trajectory** — monthly sparkline per contact showing whether message frequency is *growing*, *fading*, or *stable*.
35
- - **Loading overlay** — an animated full-screen splash with rotating quotes while the dashboard fetches data.
11
+ ![WaCrawl Dashboard, showing the invented demo archive](docs/dashboard.png)
36
12
 
37
- ### People
13
+ | Group profile | Year in Review |
14
+ | :-- | :-- |
15
+ | ![Group profile](docs/group-profile.png) | ![Year in Review](docs/year-in-review.png) |
38
16
 
39
- - Contact-style list ranked by activity, with message, media, sent, and received counts and last message time. Privacy-oriented display for LID-style identifiers (JID hidden when appropriate).
17
+ Every name and number in these screenshots is invented. They come from the demo archive below.
40
18
 
41
- ### Chats
19
+ ## Quick start
42
20
 
43
- - **Two-pane browser** — conversation list (with kind: direct vs group, preview line, counts, last activity) and the latest messages for the selected chat.
44
- - **Read-only inspection** — message bubbles, timestamps, optional **copy message** (including media-aware clipboard text), and inline **images / videos / audio** when paths resolve under your configured media root.
45
- - **Deep links** — `?contact=<jid>` (for example from the dashboard top-contacts chart) selects the matching chat when present.
21
+ ```bash
22
+ npx wacrawl-dashboard@latest
23
+ ```
46
24
 
47
- ### Media & Links
25
+ That's it. It opens on `http://127.0.0.1:3001`. You need **Node 20+** and a WaCrawl archive at `~/.wacrawl/wacrawl.db`.
48
26
 
49
- - **Two-tab layout** — switch between **Media Grid** and **Link Intelligence** from the page header.
50
- - **Media Grid** — paged grid of indexed media with **infinite scroll** (intersection-based loading). Thumbnails for **images** and **videos**, affordances for **audio**, and a full-screen **lightbox** for preview and playback where the type supports it.
51
- - **Link Intelligence** (new) — a rich analytics panel for URLs shared in your conversations:
52
- - **Overview pills** — total links shared and unique domains at a glance.
53
- - **Domain leaderboard** — horizontal bar chart of the most-shared domains, colour-coded by category.
54
- - **Content categories** — pie chart breaking links into *Video, Social, News, Shopping, Music, Developer, Reference,* and *Other*, with the top domain per category.
55
- - **Media timeline density** — stacked area chart showing daily counts of images, videos, audio files, and links over time.
56
- - **Sharing asymmetry** — per-contact breakdown of media and link volume in each direction (sent vs received).
57
- - **First shared** — a timeline of the earliest message and earliest media item for each conversation.
27
+ Keep your archive somewhere else?
58
28
 
59
- ### Search
29
+ ```bash
30
+ WACRAWL_DB=/path/to/wacrawl.db npx wacrawl-dashboard@latest
31
+ ```
60
32
 
61
- - **FTS-backed** full-text search over message content (plus chat and sender names) against the local archive; debounced queries after two or more characters.
62
- - Result rows show context, optional **image/video thumbnails** and **inline audio**, link detection in snippets, **copy**, and media type badges. Optimized DB (below) is recommended for best performance.
33
+ No archive yet? Install [wacrawl](https://github.com/steipete/wacrawl) and run `wacrawl sync` first. The dashboard reads the archive; it doesn't create it.
63
34
 
64
- ### Settings
35
+ ### Just looking?
65
36
 
66
- - Edit **archive paths** (WhatsApp shared container, primary WaCrawl DB, chat DB, contacts DB, media root) with **save** and **reset to `.env` defaults**.
67
- - **Health** banner shows whether the primary database opens successfully and the resolved path.
68
- - Effective paths combine **environment variables** with optional overrides persisted to `~/.wacrawl/dashboard-paths.json` so `<img>` / `<video>` URLs work without custom browser headers.
37
+ ```bash
38
+ npx wacrawl-dashboard@latest --demo
39
+ ```
69
40
 
70
- ### App experience
41
+ This generates a throwaway archive of invented messages in a temp folder, and deletes it when you quit. A **Demo data** badge stays in the top bar. It never reads `~/.wacrawl` or your `.env`, Refresh won't start `wacrawl sync`, and anything you change in Settings goes to a temp file rather than your saved settings.
42
+
43
+ ## What's inside
44
+
45
+ ### 🏠 Dashboard: the big picture
46
+
47
+ - **Overview cards.** Total messages, chats, contacts, media files and archive date span. Each card is also a shortcut to Search, Chats, People or Media.
48
+ - **Time window.** The top-bar filter (**Day / Week / Month / Year / All**) drives the charts. It defaults to **Year**. The **activity heatmap** always shows the current calendar year.
49
+ - **Timezone-aware.** Hour-of-day and heatmap buckets use *your* browser's timezone, not UTC. Your 11 p.m. texting habit is recorded at 11 p.m., where it belongs.
50
+ - **Charts, charts, charts.** Message volume over time, top contacts, hour-of-day and day-of-week activity, monthly sent vs. received, media breakdown by count and bytes, top media senders, busiest groups, and messaging streaks (current and longest).
51
+ - **Drill-downs.** Click a heatmap day, an hour or weekday bar, a weekly-rhythm slot, a volume or sent/received point, a top contact, or a media or group row to open the messages behind it in Search, scoped to the current period. Cards with nothing meaningful to filter by are no longer clickable.
52
+ - **Weekly rhythm.** An hour × weekday grid, so "Sunday night" shows up as the peak it is instead of being averaged away into two separate bar charts.
53
+ - **How fast replies happen.** Every point where a conversation changed hands, bucketed by reply time and split into **You** vs. **Them**. It's data now, so you can't say "I was busy."
54
+ - **Drifted away.** People you used to message regularly who have gone quiet, comparing recent traffic against an earlier baseline. Mildly poignant. Maybe text them?
55
+ - **Buried, not dead.** How many chats you've archived, and which archived ones are still getting messages anyway. You tried.
56
+ - **Word cloud.** Top terms, with an **All words** vs. **Useful words** (stopwords removed) toggle. Click a term to search for it.
57
+ - **Distinctive words.** Not your *most common* words, the ones that mark a message as yours (or as theirs), ranked by log-odds so everyday words cancel out. Often it's just your name, as said by everyone else.
58
+ - **Emoji analytics.** Total and unique emoji, top emoji by **All / Sent / Received**, and a leaderboard of who uses emoji most, with each person's favourite.
59
+ - **Conversation dynamics:**
60
+ - **Who starts conversations.** Initiation ratio per contact. A session starts after a gap of 4 hours or more.
61
+ - **Conversation depth.** Average messages per session, so you can see which chats go deep and which stop at "k."
62
+ - **Ghost score.** The percentage of your messages that got no reply within 24 hours, colour-coded by severity. 👻
63
+ - **Late-night texters.** Who you talk to between midnight and 5 a.m. No judgement. Some judgement.
64
+ - **Relationship trajectory.** A monthly sparkline per contact showing whether things are *growing*, *fading* or *stable*.
65
+ - **Loading splash.** Rotating quotes to keep you company while SQLite does the heavy lifting.
66
+
67
+ ### 👥 People and contact profiles
68
+
69
+ - A contact list ranked by activity, filterable by name (server-side, `?q=`), with message, media, sent and received counts and the time of the last message. LID-style identifiers are hidden when showing them would reveal nothing useful.
70
+ - Click anyone to open their **profile** at `/contacts/:jid`:
71
+ - **Balance.** Who does more of the talking.
72
+ - **Turnaround.** How fast each of you replies.
73
+ - **Volume by month.**
74
+ - **When you talk.** By hour and weekday.
75
+ - **Media exchanged.**
76
+ - **What you talk about.** Their personal word and emoji cloud.
77
+
78
+ ### 🗣️ Groups and group profiles
79
+
80
+ - Every group, filterable by name (`?q=`) and sortable by **Recently active**, **Most members** or **Most messages**.
81
+ - **Group graveyard** (on the Dashboard). The share of your groups with no message in 180+ days, median group age, the oldest group still alive, groups founded per year split into *still active* vs. *silent*, and the once-loudest groups that went quiet.
82
+ - Each group gets a **profile** at `/groups/:jid`:
83
+ - **Who carries it.** The top talkers.
84
+ - **The room.** The member roster.
85
+ - **Concentration.** How much of the chat comes from a handful of people. Spoiler: usually a handful.
86
+ - **Volume by month** and **When it talks.**
87
+ - **Never posted.** The lurkers, named.
88
+ - **Media shared** and **What it talks about.**
89
+ - The dashboard works out which roster entry is *you*, even though WhatsApp never records your own JID in groups. You won't show up as the quietest member of all your groups. (An earlier version did exactly that, and the lurker report named the person reading it.)
90
+
91
+ ### 🎁 Year in Review
92
+
93
+ A Wrapped-style recap for any year in your archive (`/years/:year`): headline totals, busiest day and month, a year-over-year comparison, top chats, chats that started that year, and a media summary. It's like Spotify Wrapped, except you are the artist and the song is "sorry, just saw this."
94
+
95
+ ### 💬 Chats
96
+
97
+ - **Two-pane browser.** The conversation list on the left (direct vs. group, preview line, counts, last activity) and the messages on the right.
98
+ - **Read-only inspection.** Message bubbles, timestamps, **copy message** (media-aware), and inline **images, videos and audio** when their paths resolve under your media root.
99
+ - **Filters.** A name filter and an All / Direct / Groups switch above the list (`?q=`, `?kind=`). The open chat stays open while you filter.
100
+ - **Deep links.** `?contact=<jid>` preselects a chat, which is how a search result jumps straight to its message.
101
+
102
+ ### 🖼️ Media and links
103
+
104
+ - **Media grid.** Infinite-scrolling thumbnails for images and videos, controls for audio, and a full-screen lightbox.
105
+ - **Link intelligence**, a full report on every URL anyone has sent you:
106
+ - Total links and unique domains.
107
+ - A **domain leaderboard**, colour-coded by category.
108
+ - **Content categories:** *Video, Social, News, Shopping, Music, Developer, Reference* and *Other*. Yes, you can finally measure how many YouTube links your uncle sends.
109
+ - **Media timeline.** Daily images, videos, audio and links, stacked.
110
+ - **Sharing asymmetry.** Who sends you the most media and links, and how many you send back.
111
+ - **First shared.** The earliest message and earliest media item in each conversation.
112
+
113
+ ### 🔎 Search
114
+
115
+ - Full-text search (FTS5) over message content, chat names and sender names. Queries are debounced and start after two characters, or run with no text at all once a filter is set.
116
+ - **Filters, kept in the URL.** Chat, sender (including yourself), media type, rolling period, date range, weekday and hour, read in your timezone. Results show the total count, load 50 more at a time, and highlight the matched terms.
117
+ - `GET /api/search` takes `q`, `chat`, `sender` (a JID or `me`), `contact` (both directions with one person, as Top Contacts counts them), `mediaType` (a type or `any`), `period`, `from`, `to`, `hour`, `weekday` and `timeZone`. Invalid values return a `400`.
118
+ - Results show context, image and video thumbnails, inline audio, clickable links, media badges and a copy button.
119
+
120
+ ### ⚙️ Settings
121
+
122
+ - Edit **archive paths** (WhatsApp shared container, primary WaCrawl DB, chat DB, contacts DB, media root), then **save** or **reset to `.env` defaults**.
123
+ - A **health** banner tells you whether the primary database opens, and at which resolved path.
124
+ - Overrides are saved to `~/.wacrawl/dashboard-paths.json`, so `<img>` and `<video>` URLs work without custom browser headers.
125
+
126
+ ### 🔄 Refresh
127
+
128
+ - The **Refresh button** runs `wacrawl sync` to import new messages, then reloads every panel. The icon spins while the import runs and the button is disabled, so impatient double-clicks do nothing.
129
+ - A **"Synced 3 hours ago"** stamp, read from the archive's own `sync_state` table.
130
+ - **Automatic refresh** when the app opens, and when the tab regains focus after five or more minutes hidden. Automatic refresh only *refetches*. It never starts an import, so nothing writes to your archive behind your back.
131
+ - **Errors are shown, not swallowed.** If `wacrawl sync` refuses (most often with `source contains multiple WhatsApp account identities`), you see the CLI's own message and the next step. The dashboard never passes `--adopt-source` for you, because that flag rewrites which account owns the archive, and that's your call.
132
+ - Only one import runs at a time. A second request waits for the first rather than starting a competing writer.
133
+ - **The archive is derived data.** The `wacrawl` CLI owns every write; the dashboard only reads. Treat the WhatsApp source, not the archive, as your source of truth.
134
+
135
+ ### ⌨️ Keyboard shortcuts
136
+
137
+ **Meta** is **⌘** on macOS and **Win** on most Windows keyboards.
138
+
139
+ | Shortcut | Goes to |
140
+ | :-- | :-- |
141
+ | **Meta+K** | Search |
142
+ | **Meta+1** | Dashboard |
143
+ | **Meta+2** | People |
144
+ | **Meta+3** | Chats |
145
+ | **Meta+4** | Groups |
146
+ | **Meta+5** | Media |
147
+ | **Meta+6** | Search |
148
+ | **Meta+7** | Year in Review |
149
+ | **Meta+8** | Settings |
150
+
151
+ Also: **dark and light themes** (toggle in the top bar), and a **welcome/help modal** on first visit that you can reopen from **Help**.
152
+
153
+ ## 🔒 Privacy and safety
154
+
155
+ We've taken more care with this than your group chat takes with spoilers.
156
+
157
+ - The API listens on **`127.0.0.1` only**. When it serves the dashboard itself (`npx wacrawl-dashboard`), only that dashboard's own origin may call it, so a site on another local port can't read your archive. Under `npm run dev` and `npm run preview`, any loopback origin is allowed, because the UI comes from Vite on a port that can move.
158
+ - **Attachments can't run in the dashboard.** Photos, video, audio and PDFs open in the browser. Anything else, including HTML and SVG, downloads instead, because anyone can send you a file and the API serves it from the same origin as your archive.
159
+ - The archive is opened **read-only**. `/api/health` reports whether the DB is readable and which paths it resolved.
160
+ - `POST /api/sync` is the only route that starts a process, so it requires an **`X-Wacrawl-Request: 1`** header. CORS stops a cross-origin page from *reading* a response, but not from *sending* a simple POST. A non-simple header forces a preflight, and the origin check rejects it. The CLI is spawned without a shell and with a fixed argument list.
161
+ - Set `WACRAWL_DISABLE_SYNC=1` and the API never spawns anything at all.
162
+ - Stats responses are cached in memory, keyed on the archive's path, mtime and size, with a 5-minute TTL. Re-syncing or switching archives invalidates the cache.
71
163
 
72
- - **Welcome / help** modal on first visit (stored in `localStorage`); reopen from **Help** in the sidebar or top bar.
73
- - **Dark and light** themes (toggle in the top bar).
74
- - **Keyboard shortcuts** use the **Meta** key (**⌘** on macOS, **Win** on many Windows keyboards): **Meta+K** opens Search; **Meta+1** Dashboard, **Meta+2** People, **Meta+3** Chats, **Meta+4** Media, **Meta+5** Search, **Meta+6** Settings.
164
+ ## 🏗️ How it's built
75
165
 
76
- ### API and safety
166
+ A small npm-workspaces monorepo:
77
167
 
78
- - Listens on **`127.0.0.1`** with host and CORS restricted to local dev/preview origins.
79
- - Opens the WaCrawl SQLite database in **readonly** mode where applicable; `/api/health` reports DB readability and resolved paths.
80
- - **Stats endpoints** — `/api/stats/emoji-analytics`, `/api/stats/conversation-dynamics`, and `/api/stats/link-intelligence` power the new dashboard and media analytics panels.
168
+ ```
169
+ apps/
170
+ api/ Express 5 + better-sqlite3, read-only, TypeScript, Vitest
171
+ web/ React + Vite + Tailwind + Recharts + Zustand
172
+ bin/ the `npx wacrawl-dashboard` launcher (serves the built web app from the API)
173
+ e2e/ Playwright smoke tests, screenshots and the fresh-install check
174
+ docs/ PRD, npm publishing notes, screenshots
175
+ ```
81
176
 
82
- ## Requirements
177
+ ### API endpoints
83
178
 
84
- - Node.js 20+
85
- - A WaCrawl archive at `~/.wacrawl/wacrawl.db` (or another path you configure)
179
+ | Area | Routes |
180
+ | :-- | :-- |
181
+ | **Data** | `/api/people`, `/api/chats`, `/api/chats/:jid/messages`, `/api/media`, `/api/media/file`, `/api/search`, `/api/messages/:id/offset` |
182
+ | **Stats** | `/api/stats/` + `overview`, `top-contacts`, `message-volume`, `activity-heatmap`, `hour-of-day`, `day-of-week`, `media-breakdown`, `media-senders`, `sent-received-ratio`, `response-times`, `reply-latency`, `group-activity`, `streaks`, `word-cloud`, `emoji-analytics`, `conversation-dynamics`, `year-in-review`, `contact-profile`, `dormancy`, `groups`, `group-profile`, `link-intelligence`, `weekly-rhythm`, `distinctive-words`, `group-lifecycle`, `archived-chats` |
183
+ | **Sync** | `GET /api/sync/status`, `POST /api/sync` (returns `202`, or `409` if an import is already running) |
184
+ | **Settings** | `GET`/`POST /api/settings/paths` |
86
185
 
87
- ## Development
186
+ ## 🛠️ Development
88
187
 
89
188
  ```bash
90
189
  npm install
91
190
  npm run dev
92
191
  ```
93
192
 
94
- The API runs on `http://127.0.0.1:3001` and the Vite app runs on `http://localhost:5173`.
193
+ The API runs on `http://127.0.0.1:3001` and Vite on `http://localhost:5173`.
194
+
195
+ | Script | What it does |
196
+ | :-- | :-- |
197
+ | `npm run dev` | API + web with hot reload |
198
+ | `npm run build` | Production build (API, then web) |
199
+ | `npm run preview` | Serve the built web app locally |
200
+ | `npm test` | API unit tests (Vitest) |
201
+ | `npm run demo` | Run the built app against a generated demo archive |
202
+ | `npm run test:e2e` | Playwright smoke tests against the built app and demo archive (run `npm run build` first; `npx playwright install chromium` once) |
203
+ | `npm run test:fresh-install` | Pack the build, install the tarball in a temp folder, and run `--demo` from it |
204
+ | `npm run docs:screenshots` | Regenerate the README screenshots from the demo archive |
205
+ | `npm run demo:generate -w @wacrawl/api -- --out <dir> --seed <n> --now <ISO date>` | Write a demo archive. The same seed and date give the same rows |
206
+ | `npm run typecheck` | TypeScript checks for both workspaces |
207
+ | `npm run lint` / `lint:fix` | ESLint |
208
+ | `npm run verify` | Lint + typecheck + unit tests |
209
+ | `npm run verify:full` | `verify`, then build, smoke tests and the fresh-install check: the same gate CI runs |
210
+
211
+ CI (GitHub Actions) runs `verify:full`'s steps on Node 20, 22 and 24 on every push and PR. Publishing a GitHub release ships to npm with provenance. See [docs/how to publish to npm.md](docs/how%20to%20publish%20to%20npm.md).
212
+
213
+ > **Test data note:** unit tests use a synthetic in-memory archive (`apps/api/src/__tests__/testDb.ts`), and smoke tests and screenshots use the generated one (`apps/api/src/demo/`). Please keep it that way. Nobody wants their real group chat immortalised in a test fixture.
214
+
215
+ ## 🧩 Configuration
216
+
217
+ Copy `apps/api/.env.example` to `apps/api/.env` (and optionally `apps/web/.env.example` to `apps/web/.env.local`), then adjust the paths.
218
+
219
+ | Variable | Purpose |
220
+ | :-- | :-- |
221
+ | `WACRAWL_DB` | Primary SQLite archive the dashboard queries (from `wacrawl sync`). Default: `~/.wacrawl/wacrawl.db` |
222
+ | `WACRAWL_WHATSAPP_CONTAINER` | WhatsApp shared container folder (e.g. `~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared`). Used to derive the chat, contacts and media paths below when they're unset |
223
+ | `WACRAWL_CHAT_DB` / `WACRAWL_CONTACTS_DB` | Explicit paths to `ChatStorage.sqlite` and `ContactsV2.sqlite` |
224
+ | `WACRAWL_MEDIA_ROOT` | Folder with downloaded media (often `…/Message/Media`). Relative archive paths resolve against it |
225
+ | `WACRAWL_PATHS_FILE` | JSON file for UI-saved overrides. Default: `~/.wacrawl/dashboard-paths.json` |
226
+ | `WACRAWL_BIN` | Path to the `wacrawl` executable if it's not on `PATH` |
227
+ | `WACRAWL_DISABLE_SYNC` | Set to `1` and Refresh only reloads data. It never spawns `wacrawl sync` |
228
+ | `WACRAWL_DEMO` | Set by `--demo`; turns on the **Demo data** badge. You shouldn't need to set it yourself |
229
+ | `PORT` | API port. Default: `3001`. `npm run dev` and `npm run preview` expect `3001`; for another port under dev, also set `VITE_API_URL` |
230
+ | `VITE_API_URL` (web) | API base URL. Default: `http://127.0.0.1:3001` under `npm run dev`, the page's own origin in a production build. A value set at build time is baked in |
231
+
232
+ You can also change paths from **Settings** (**Meta+8**) instead of editing files.
233
+
234
+ Run the API once against another archive:
95
235
 
96
- Other root scripts:
97
-
98
- - **`npm run build`** — build API then web for production.
99
- - **`npm run preview`** — serve the built web app (Vite preview, localhost).
100
- - **`npm run test`** — API unit tests (Vitest).
101
- - **`npm run typecheck`** — TypeScript checks for both workspaces.
236
+ ```bash
237
+ WACRAWL_DB=/path/to/wacrawl.db npm run dev -w @wacrawl/api
238
+ ```
102
239
 
103
- ## Usage (npx one-liner)
240
+ ## ⚡ Search optimization (probably not needed)
104
241
 
105
- The simplest way to run the dashboard is a single command:
242
+ **Recent wacrawl versions don't need this.** wacrawl 0.3.9+ builds and maintains its own `messages_fts(text, chat, sender, media)` table on every import, so Search stays current after each Refresh. Check whether yours has it:
106
243
 
107
244
  ```bash
108
- npx wacrawl-dashboard@latest
245
+ sqlite3 ~/.wacrawl/wacrawl.db "SELECT COUNT(*) FROM messages_fts;"
109
246
  ```
110
247
 
111
- This installs and launches the latest published version directly from npm, no cloning or setup required. Point it at a different WaCrawl archive with the `WACRAWL_DB` environment variable:
248
+ If that errors, your archive is from an older wacrawl, and you can add indexes and an FTS5 table yourself:
112
249
 
113
250
  ```bash
114
- WACRAWL_DB=/path/to/wacrawl.db npx wacrawl-dashboard@latest
251
+ npm run optimize-db -w @wacrawl/api
115
252
  ```
116
253
 
117
- The dashboard opens on `http://127.0.0.1:3001` and requires Node 20+.
118
-
119
- ## Configuration
120
-
121
- Copy `apps/api/.env.example` to `apps/api/.env` (and optionally `apps/web/.env.example` to `apps/web/.env.local`) and edit paths for your WhatsApp container and WaCrawl database.
122
-
123
- Supported variables:
254
+ (Prefix it with `WACRAWL_DB=/path/to/wacrawl.db` to target another archive.)
124
255
 
125
- - **`WACRAWL_WHATSAPP_CONTAINER`** — folder such as `Library/Group Containers/group.net.whatsapp.WhatsApp.shared`; used to derive default chat, contacts, and media paths when those variables are omitted.
126
- - **`WACRAWL_CHAT_DB`** / **`WACRAWL_CONTACTS_DB`** — explicit paths to `ChatStorage.sqlite` and `ContactsV2.sqlite`.
127
- - **`WACRAWL_MEDIA_ROOT`** — folder containing downloaded media (often `…/Message/Media`); relative paths in the archive resolve against this directory.
128
- - **`WACRAWL_DB`** — primary SQLite file the dashboard queries (WaCrawl export schema from `wacrawl sync`). Defaults to `~/.wacrawl/wacrawl.db`.
129
- - **`WACRAWL_PATHS_FILE`** (optional) — JSON file for UI-saved path overrides; defaults to `~/.wacrawl/dashboard-paths.json`.
256
+ ⚠️ `optimize-db` adds btree indexes (`idx_messages_chat_jid`, `idx_contacts_jid`, `idx_contacts_lid`), but it also **drops and replaces** `messages_fts` with a five-column version of its own. On a wacrawl that maintains its own FTS table, that replacement may not survive the next import, and may break it. Only run it on archives that don't ship an FTS table.
130
257
 
131
- On the web side, **`VITE_API_URL`** (default `http://127.0.0.1:3001`) sets the API base URL if your dev setup differs.
258
+ ## 🙏 Credits
132
259
 
133
- You can also open **Settings** in the sidebar (or **Meta+6**): overrides are saved to `~/.wacrawl/dashboard-paths.json` so the API picks them up without relying on browser-only headers (needed for `<img>` / `<video>` URLs).
260
+ Built on top of [wacrawl](https://github.com/steipete/wacrawl) by Peter Steinberger, which does the hard part of getting your messages out of WhatsApp. This project just stares at them thoughtfully and makes charts.
134
261
 
135
- To run the API once with env overrides:
136
-
137
- ```bash
138
- WACRAWL_DB=/path/to/wacrawl.db npm run dev -w @wacrawl/api
139
- ```
140
-
141
- ## Search optimization
142
-
143
- Prepare the SQLite archive with supporting indexes and an FTS5 search table:
144
-
145
- ```bash
146
- npm run optimize-db -w @wacrawl/api
147
- ```
262
+ ## License
148
263
 
149
- Use `WACRAWL_DB=/path/to/wacrawl.db` with the same command to optimize a non-default archive. Re-run this after `wacrawl sync` so the FTS table includes newly synced messages.
264
+ MIT. Use it, fork it, and ask it who your real friends are. Just don't blame us for the answer. 🫠