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.
- package/README.md +220 -105
- package/apps/api/dist/demo/demoArchive.js +371 -0
- package/apps/api/dist/demo/demoCast.js +138 -0
- package/apps/api/dist/demo/demoMedia.js +108 -0
- package/apps/api/dist/index.js +109 -29
- package/apps/api/dist/lib/archiveSchema.js +57 -0
- package/apps/api/dist/lib/archiveSync.js +151 -0
- package/apps/api/dist/lib/chatKind.js +41 -0
- package/apps/api/dist/lib/displayName.js +20 -0
- package/apps/api/dist/lib/mediaFsPath.js +27 -0
- package/apps/api/dist/lib/percentile.js +23 -0
- package/apps/api/dist/lib/query.js +33 -7
- package/apps/api/dist/lib/rollupDb.js +253 -0
- package/apps/api/dist/lib/searchFilters.js +141 -0
- package/apps/api/dist/lib/selfIdentity.js +42 -0
- package/apps/api/dist/lib/statsCache.js +83 -0
- package/apps/api/dist/lib/timezone.js +102 -0
- package/apps/api/dist/lib/wordCloudUsefulStopWords.js +36 -3
- package/apps/api/dist/pathsResolve.js +17 -3
- package/apps/api/dist/routes/data.js +141 -77
- package/apps/api/dist/routes/settings.js +3 -0
- package/apps/api/dist/routes/stats/profiles.js +522 -0
- package/apps/api/dist/routes/stats/reports.js +555 -0
- package/apps/api/dist/routes/stats/shared.js +75 -0
- package/apps/api/dist/routes/stats.js +273 -323
- package/apps/api/dist/routes/sync.js +60 -0
- package/apps/api/dist/scripts/generateDemoArchive.js +24 -0
- package/apps/api/package.json +31 -0
- package/apps/web/dist/apple-touch-icon.png +0 -0
- package/apps/web/dist/assets/charts-BMGh5n-d.js +36 -0
- package/apps/web/dist/assets/icons-BdElrSaU.js +1 -0
- package/apps/web/dist/assets/index-Bb8Zj42D.css +1 -0
- package/apps/web/dist/assets/index-wrxENU1e.js +9 -0
- package/apps/web/dist/assets/react-D3RL6ob4.js +3 -0
- package/apps/web/dist/favicon-16x16.png +0 -0
- package/apps/web/dist/favicon-32x32.png +0 -0
- package/apps/web/dist/favicon-512x512.png +0 -0
- package/apps/web/dist/favicon.ico +0 -0
- package/apps/web/dist/favicon.svg +59 -0
- package/apps/web/dist/index.html +11 -2
- package/bin/wacrawl-dashboard.js +64 -15
- package/package.json +20 -4
- package/apps/web/dist/assets/index-CFh_Wz98.js +0 -43
- 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
|
-
|
|
3
|
+
> *Your WhatsApp history, finally looked at by someone other than you at 2 a.m.*
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-

|
|
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
|
-
|
|
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
|
-
|
|
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
|
+

|
|
36
12
|
|
|
37
|
-
|
|
13
|
+
| Group profile | Year in Review |
|
|
14
|
+
| :-- | :-- |
|
|
15
|
+
|  |  |
|
|
38
16
|
|
|
39
|
-
|
|
17
|
+
Every name and number in these screenshots is invented. They come from the demo archive below.
|
|
40
18
|
|
|
41
|
-
|
|
19
|
+
## Quick start
|
|
42
20
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npx wacrawl-dashboard@latest
|
|
23
|
+
```
|
|
46
24
|
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
```bash
|
|
30
|
+
WACRAWL_DB=/path/to/wacrawl.db npx wacrawl-dashboard@latest
|
|
31
|
+
```
|
|
60
32
|
|
|
61
|
-
|
|
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
|
-
###
|
|
35
|
+
### Just looking?
|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
37
|
+
```bash
|
|
38
|
+
npx wacrawl-dashboard@latest --demo
|
|
39
|
+
```
|
|
69
40
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
A small npm-workspaces monorepo:
|
|
77
167
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
177
|
+
### API endpoints
|
|
83
178
|
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
##
|
|
240
|
+
## ⚡ Search optimization (probably not needed)
|
|
104
241
|
|
|
105
|
-
|
|
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
|
-
|
|
245
|
+
sqlite3 ~/.wacrawl/wacrawl.db "SELECT COUNT(*) FROM messages_fts;"
|
|
109
246
|
```
|
|
110
247
|
|
|
111
|
-
|
|
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
|
-
|
|
251
|
+
npm run optimize-db -w @wacrawl/api
|
|
115
252
|
```
|
|
116
253
|
|
|
117
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
258
|
+
## 🙏 Credits
|
|
132
259
|
|
|
133
|
-
|
|
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
|
-
|
|
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
|
|
264
|
+
MIT. Use it, fork it, and ask it who your real friends are. Just don't blame us for the answer. 🫠
|