waidrop-cli 1.0.0__py3-none-any.whl
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.
- waidrop_cli/__init__.py +3 -0
- waidrop_cli/__main__.py +4 -0
- waidrop_cli/archive.py +103 -0
- waidrop_cli/auth.py +321 -0
- waidrop_cli/auth_flow.py +182 -0
- waidrop_cli/chats.py +169 -0
- waidrop_cli/cli.py +733 -0
- waidrop_cli/config.py +359 -0
- waidrop_cli/context.py +352 -0
- waidrop_cli/crypto.py +126 -0
- waidrop_cli/dropbox.py +217 -0
- waidrop_cli/env_file.py +132 -0
- waidrop_cli/errors.py +21 -0
- waidrop_cli/fetch.py +214 -0
- waidrop_cli/mcp_cli.py +293 -0
- waidrop_cli/mcp_config.py +191 -0
- waidrop_cli/mcp_server.py +367 -0
- waidrop_cli/media.py +179 -0
- waidrop_cli/output.py +160 -0
- waidrop_cli-1.0.0.dist-info/METADATA +477 -0
- waidrop_cli-1.0.0.dist-info/RECORD +25 -0
- waidrop_cli-1.0.0.dist-info/WHEEL +4 -0
- waidrop_cli-1.0.0.dist-info/entry_points.txt +3 -0
- waidrop_cli-1.0.0.dist-info/licenses/LICENSE +201 -0
- waidrop_cli-1.0.0.dist-info/licenses/NOTICE +6 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: waidrop-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Query encrypted WAiDrop archives in Dropbox by name/number and time window
|
|
5
|
+
Project-URL: Homepage, https://github.com/thabiger/waidrop-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/thabiger/waidrop-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/thabiger/waidrop-cli/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/thabiger/waidrop-cli/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Tomasz Habiger <tomasz.habiger@gmail.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
License-File: NOTICE
|
|
13
|
+
Keywords: archive,backup,cli,dropbox,privacy,whatsapp
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Topic :: Communications :: Chat
|
|
26
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: cryptography>=42
|
|
29
|
+
Requires-Dist: tzdata>=2025.2; sys_platform == 'win32'
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# waidrop-cli
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/waidrop-cli/)
|
|
37
|
+
[](https://github.com/thabiger/waidrop-cli/actions/workflows/ci.yml)
|
|
38
|
+
[](https://pypi.org/project/waidrop-cli/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
Query the WhatsApp archive written by the **WAiDrop** Chrome extension into a
|
|
42
|
+
Dropbox App Folder. Fetch messages for one or more conversations — by phone number,
|
|
43
|
+
contact name (wildcards supported), chat hash or JID — over a date- or hour-precise
|
|
44
|
+
window.
|
|
45
|
+
|
|
46
|
+
The CLI talks only to Dropbox and runs entirely locally. It decrypts encrypted
|
|
47
|
+
archives with your archive key; no server is involved.
|
|
48
|
+
|
|
49
|
+
> **Companion extension:** the WAiDrop Chrome extension is required to create
|
|
50
|
+
> the archive. The store link will be added here.
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Python ≥ 3.10.
|
|
55
|
+
- An encrypted WhatsApp archive created by the WAiDrop Chrome extension.
|
|
56
|
+
- The archive key generated by the extension.
|
|
57
|
+
- A Dropbox account connected to the same Dropbox app. The built-in app key is
|
|
58
|
+
used by default.
|
|
59
|
+
|
|
60
|
+
On Windows, the `tzdata` package is installed automatically so `--tz` can use
|
|
61
|
+
IANA timezones.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
pip install waidrop-cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Other installers:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
uv tool install waidrop-cli
|
|
73
|
+
pipx install waidrop-cli
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
From source:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
git clone https://github.com/thabiger/waidrop-cli.git
|
|
80
|
+
cd waidrop-cli
|
|
81
|
+
uv sync
|
|
82
|
+
uv run waidrop --help
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Upgrade or remove:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pip install --upgrade waidrop-cli
|
|
89
|
+
uv tool upgrade waidrop-cli
|
|
90
|
+
|
|
91
|
+
pip uninstall waidrop-cli
|
|
92
|
+
uv tool uninstall waidrop-cli
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Dropbox setup
|
|
96
|
+
|
|
97
|
+
The CLI uses **OAuth 2.0 with PKCE** and the same Dropbox app as the Chrome
|
|
98
|
+
extension, so both see the same App Folder. The built-in app key is a public
|
|
99
|
+
client id; no client secret is involved for the built-in app. Each user
|
|
100
|
+
authorizes once and the CLI stores a long-lived refresh token locally.
|
|
101
|
+
|
|
102
|
+
### Connect the CLI
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
waidrop auth
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This opens a browser (or prints a URL), asks you to click **Allow** on Dropbox,
|
|
109
|
+
and prompts for the authorization code. Credentials are stored at
|
|
110
|
+
`~/.config/waidrop/credentials.json` (file mode `0600`, directory `0700`).
|
|
111
|
+
On Windows the default is `%APPDATA%\waidrop\credentials.json`.
|
|
112
|
+
|
|
113
|
+
On headless hosts or over SSH:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
waidrop auth --no-browser # print URL only
|
|
117
|
+
waidrop auth --code <CODE> # non-interactive (pass the code)
|
|
118
|
+
waidrop auth --loopback # localhost callback (desktop)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For `--loopback`, register `http://127.0.0.1:53682/callback` as a redirect URI
|
|
122
|
+
in the Dropbox App Console. The paste flow needs no redirect URI.
|
|
123
|
+
|
|
124
|
+
Check or remove the connection:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
waidrop auth --status # human-readable
|
|
128
|
+
waidrop auth --status --json # machine-readable
|
|
129
|
+
waidrop auth --revoke # revoke remote access + delete local credentials
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`auth --revoke` only deletes local credentials after a successful remote
|
|
133
|
+
revoke. If Dropbox cannot be reached, the local credentials are kept and the
|
|
134
|
+
command exits with code `5`.
|
|
135
|
+
|
|
136
|
+
### Custom Dropbox apps
|
|
137
|
+
|
|
138
|
+
The built-in app key is `ydtsl7okrf4xuee`. To use your own app, create a
|
|
139
|
+
**Scoped access** app with **App folder** access in the
|
|
140
|
+
[Dropbox App Console](https://www.dropbox.com/developers/apps), enable and
|
|
141
|
+
submit these permissions:
|
|
142
|
+
|
|
143
|
+
- `account_info.read` (account display)
|
|
144
|
+
- `files.metadata.read` (list folders/files)
|
|
145
|
+
- `files.content.read` (downloads; used by this CLI)
|
|
146
|
+
- `files.content.write` (uploads + folder creation; used by the extension)
|
|
147
|
+
|
|
148
|
+
Use PKCE; never ship a client secret. Set the app key with
|
|
149
|
+
`--dropbox-app-key` or `WAIDROP_DROPBOX_APP_KEY`.
|
|
150
|
+
|
|
151
|
+
> **Production note:** Dropbox apps in development mode are limited to test
|
|
152
|
+
> users, and some scopes require review. Verify the app status and scopes before
|
|
153
|
+
> announcing the CLI publicly.
|
|
154
|
+
|
|
155
|
+
> **Old archives:** a Dropbox App Folder is namespaced per app. If your archive
|
|
156
|
+
> was created with a different app key, pass `--dropbox-app-key <old-key>` or
|
|
157
|
+
> set `WAIDROP_DROPBOX_APP_KEY` so the CLI looks in the matching folder.
|
|
158
|
+
|
|
159
|
+
## Archive format
|
|
160
|
+
|
|
161
|
+
The CLI reads `account=<alias>/_format.json` before fetching data. Only
|
|
162
|
+
encrypted archives are supported:
|
|
163
|
+
|
|
164
|
+
- **encrypted** — objects use `.enc` envelopes and keyed HMAC path hashes.
|
|
165
|
+
`--key` / `WAIDROP_KEY` is required.
|
|
166
|
+
- **legacy v2** — an archive without `_format.json` is treated as encrypted and
|
|
167
|
+
still requires a key.
|
|
168
|
+
|
|
169
|
+
Plaintext archives are not supported. An archive whose `_format.json` declares
|
|
170
|
+
`"mode": "plaintext"` is rejected with a clear error.
|
|
171
|
+
|
|
172
|
+
## Configuration
|
|
173
|
+
|
|
174
|
+
Flags override environment, which overrides the credentials file.
|
|
175
|
+
|
|
176
|
+
| Setting | Flag | Env | Default |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| Dropbox access token | `--dropbox-token` | `WAIDROP_DROPBOX_TOKEN` | credentials file |
|
|
179
|
+
| Dropbox refresh token | `--dropbox-refresh-token` | `WAIDROP_DROPBOX_REFRESH_TOKEN` | credentials file |
|
|
180
|
+
| Dropbox app key | `--dropbox-app-key` | `WAIDROP_DROPBOX_APP_KEY` | built-in / credentials file |
|
|
181
|
+
| Dropbox app secret | `--dropbox-app-secret` | `WAIDROP_DROPBOX_APP_SECRET` | — (not needed for built-in) |
|
|
182
|
+
| credentials file | `--credentials-file` | `WAIDROP_CREDENTIALS` | `~/.config/waidrop/credentials.json` |
|
|
183
|
+
| archive encryption key | `--key` | `WAIDROP_KEY` | required |
|
|
184
|
+
| account alias | `--account` | `WAIDROP_ACCOUNT` | `primary` |
|
|
185
|
+
| subfolder in app folder | `--folder` | `WAIDROP_FOLDER` | app-folder root |
|
|
186
|
+
| timezone for bounds | `--tz` | — | system local |
|
|
187
|
+
| HTTP timeout (s) | `--timeout` | — | 60 |
|
|
188
|
+
| part guard | `--max-parts` | — | 1000 |
|
|
189
|
+
|
|
190
|
+
An explicit access or refresh token (flag/env) disables the credentials file so
|
|
191
|
+
secrets injected by a host are never overwritten. The archive key is the base64
|
|
192
|
+
32-byte key generated by the extension. It is required; without it the archive
|
|
193
|
+
cannot be decrypted.
|
|
194
|
+
|
|
195
|
+
Prefer environment variables or the credentials file over command-line secrets:
|
|
196
|
+
`--dropbox-token`, `--key` and `--code` can appear in shell history and process
|
|
197
|
+
listings.
|
|
198
|
+
|
|
199
|
+
## Usage
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
# store the archive key and account for MCP and headless use
|
|
203
|
+
waidrop config
|
|
204
|
+
|
|
205
|
+
# list chats (no messages fetched): names, phones, hashes, jids
|
|
206
|
+
waidrop chats
|
|
207
|
+
waidrop chats --name "*Family*"
|
|
208
|
+
|
|
209
|
+
# by contact name (glob/substring, case-insensitive) for a date range
|
|
210
|
+
waidrop query --name "Alice*" --from 2026-10-01 --to 2026-10-17
|
|
211
|
+
|
|
212
|
+
# by phone number (any format) for an hour-precise window
|
|
213
|
+
waidrop query --number "+1 555 0100" --from "2026-10-17 08:00" --to "2026-10-17 18:00"
|
|
214
|
+
|
|
215
|
+
# rolling window
|
|
216
|
+
waidrop query --name "Alice*" --last 36h
|
|
217
|
+
waidrop query --name "Alice*" --last 7d --format text
|
|
218
|
+
|
|
219
|
+
# all chats, recent window (no selectors)
|
|
220
|
+
waidrop query --last 1h
|
|
221
|
+
waidrop query --from "2026-10-17 08:00" --to "2026-10-17 18:00" --format csv
|
|
222
|
+
|
|
223
|
+
# export full-resolution images/stickers/GIFs plus manifest.ndjson
|
|
224
|
+
waidrop query --name "*" --last 24h --media-out ./waidrop-media
|
|
225
|
+
|
|
226
|
+
# multiple selectors / formats / output file
|
|
227
|
+
waidrop query --name "Alice*" --name "*Family*" --number +15550100 \
|
|
228
|
+
--from 2026-10-01 --to 2026-10-17 --format csv --out october.csv
|
|
229
|
+
|
|
230
|
+
# direct targeting
|
|
231
|
+
waidrop query --chat 0123456789abcdef --jid 15550100@c.us --last 2w
|
|
232
|
+
|
|
233
|
+
# per-chat conversation chunks for an analysis window
|
|
234
|
+
waidrop context --from 2026-10-01 --to 2026-10-17 --format text
|
|
235
|
+
waidrop context --name "Alice*" --last 6h --context-before 30m \
|
|
236
|
+
--max-chars 12000 --format text
|
|
237
|
+
|
|
238
|
+
# context chunks plus exported images/stickers/GIFs and their local paths
|
|
239
|
+
waidrop context --last 6h --media-out ./waidrop-media --format text
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Bounds
|
|
243
|
+
|
|
244
|
+
`--from` / `--to` accept, in local time (or `--tz`):
|
|
245
|
+
|
|
246
|
+
| Input | Meaning |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `2026-10-01` | date precision: `from` = 00:00; `to` includes the whole day |
|
|
249
|
+
| `2026-10-01T14` | hour precision: to includes `14:00–14:59:59.999` |
|
|
250
|
+
| `2026-10-01T14:30` | minute precision: to includes `14:30–14:30:59.999` |
|
|
251
|
+
| `2026-10-01T14:30:45` | second precision |
|
|
252
|
+
|
|
253
|
+
`--from` is required unless `--last` is used. `--to` defaults to now.
|
|
254
|
+
`--last` accepts `m` (minutes), `h` (hours), `d` (days), `w` (weeks) and cannot be
|
|
255
|
+
combined with `--from`/`--to`.
|
|
256
|
+
|
|
257
|
+
With **no selectors**, `query` and `context` cover **all chats** (e.g.
|
|
258
|
+
`--last 1h`). Chats whose `firstSeen`/`lastSeen` cannot overlap the window are
|
|
259
|
+
skipped, and only part objects whose `[firstTs, lastTs]` interval can overlap
|
|
260
|
+
the window are downloaded (legacy parts without `lastTs` use a start-anchor
|
|
261
|
+
heuristic). `--max-parts` still caps the selected parts. `--context-before`
|
|
262
|
+
expands the fetched window and therefore counts toward `--max-parts`.
|
|
263
|
+
|
|
264
|
+
### Output
|
|
265
|
+
|
|
266
|
+
- `--format ndjson` (default): archive records, one JSON object per line.
|
|
267
|
+
- `--format text`: `2026-10-17 12:00 ← Alice / Alice: message…`
|
|
268
|
+
- `--format csv`: `msgId,ts,chatName,chatPhone,senderName,direction,type,text`
|
|
269
|
+
- `--out FILE` writes to a file with mode `0600`; progress goes to stderr
|
|
270
|
+
(`--quiet` silences it).
|
|
271
|
+
- `--media-out DIR` downloads and decodes (or decrypts) full-resolution media
|
|
272
|
+
referenced by the selected records into `DIR`, plus `DIR/manifest.ndjson`
|
|
273
|
+
(override with `--media-manifest`). Files are written `0600`, directory
|
|
274
|
+
`0700`, and names are derived from the timestamp and message id only.
|
|
275
|
+
`--media-max-bytes` (default 25 MiB) skips larger objects;
|
|
276
|
+
`--media-fail-fast` aborts on the first decode/download error. Media records
|
|
277
|
+
whose status is not `stored` are reported as skipped.
|
|
278
|
+
|
|
279
|
+
Text output escapes control characters. CSV output prefixes cells that start
|
|
280
|
+
with `=`, `+`, `-`, `@`, tab or carriage return with `'` to reduce spreadsheet
|
|
281
|
+
formula injection. CSV files still contain untrusted chat data; open them with
|
|
282
|
+
care.
|
|
283
|
+
|
|
284
|
+
### Context output
|
|
285
|
+
|
|
286
|
+
`context` returns conversation-oriented chunks instead of flat records:
|
|
287
|
+
|
|
288
|
+
- `--format ndjson` (default): one JSON object per chat chunk, with `chat`,
|
|
289
|
+
`window`, `part`/`parts`, `contextBeforeMs`, `oversized` and the full message
|
|
290
|
+
records (`inWindow` marks messages inside the analysis window).
|
|
291
|
+
- `--format text`: delimited per-chat blocks with `=== CHAT … ===` /
|
|
292
|
+
`=== END CHAT (i/n) ===`; newlines inside messages are escaped as `\n`.
|
|
293
|
+
- `--context-before DURATION` includes earlier messages as context (default `0`).
|
|
294
|
+
Those messages appear in the chunk but are marked `inWindow: false` and
|
|
295
|
+
`[context]` in text output.
|
|
296
|
+
- `--max-chars N` splits a chat into `part i/n` at message boundaries when its
|
|
297
|
+
rendered message lines exceed `N` characters (default `0` = one chunk per
|
|
298
|
+
chat); headers/footers are not counted. A single oversized message is kept
|
|
299
|
+
whole in its own part and flagged `oversized: true`.
|
|
300
|
+
- `--media-out DIR` decodes or decrypts full-resolution images, stickers and GIFs
|
|
301
|
+
referenced by the emitted chunks into `DIR` and adds a `mediaExport` object to each
|
|
302
|
+
NDJSON media message:
|
|
303
|
+
`{ "status": "exported", "file": "/abs/path/...jpg", "error": null }`.
|
|
304
|
+
Paths are absolute. Text output appends `(file: "…")` for exported media and
|
|
305
|
+
`(media: <status>)` or `(media: not exported)` otherwise. The manifest defaults
|
|
306
|
+
to `DIR/manifest.ndjson`; `--media-max-bytes` and `--media-fail-fast` behave
|
|
307
|
+
like `query`. Media from `--context-before` messages is included.
|
|
308
|
+
- Chats with no messages inside the analysis window are omitted, even if they
|
|
309
|
+
have context-only messages.
|
|
310
|
+
|
|
311
|
+
Exit codes: `0` ok, `2` usage/config error, `3` no matched chats / no messages in
|
|
312
|
+
window, `4` `--max-parts` guard tripped, `5` Dropbox/network/archive read error.
|
|
313
|
+
|
|
314
|
+
## Running on a headless host
|
|
315
|
+
|
|
316
|
+
The CLI auto-refreshes access tokens from the stored refresh token, so no manual
|
|
317
|
+
rotation is needed. Run `waidrop auth` once on the host (or copy the
|
|
318
|
+
credentials file). Then store the archive key in the environment file:
|
|
319
|
+
|
|
320
|
+
```sh
|
|
321
|
+
waidrop config
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
It prompts for the key without echoing it, then for the account alias. On a
|
|
325
|
+
rerun, values already in the env file are offered as defaults: press Enter to
|
|
326
|
+
keep the existing key (shown as `[keep existing]`, never printed) or the
|
|
327
|
+
existing alias. It writes `~/.config/waidrop/env` with mode `0600` (directory
|
|
328
|
+
`0700`). Use `--account work` to set a non-primary alias, `--env-file PATH` for
|
|
329
|
+
a custom location, or set `WAIDROP_KEY` / `WAIDROP_ACCOUNT` to skip the
|
|
330
|
+
corresponding prompt. The file contains:
|
|
331
|
+
|
|
332
|
+
```sh
|
|
333
|
+
# ~/.config/waidrop/env (must be 0600)
|
|
334
|
+
WAIDROP_KEY=<base64 archive key from the extension>
|
|
335
|
+
WAIDROP_ACCOUNT=primary
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
On Windows the default is `%APPDATA%\waidrop\env`. On macOS and Linux the file
|
|
339
|
+
must not be readable or writable by group or others; the CLI and MCP server
|
|
340
|
+
reject insecure files and read only `WAIDROP_*` keys. `chats`, `query`, and
|
|
341
|
+
`context` load the file automatically, so nothing needs to be exported in your
|
|
342
|
+
shell; explicit flags and shell environment variables take precedence. The file
|
|
343
|
+
next to a custom `--credentials-file` is used when one is passed.
|
|
344
|
+
|
|
345
|
+
A cron or agent wrapper can still source the file (`set -a; .
|
|
346
|
+
~/.config/waidrop/env; set +a`) before running other tools:
|
|
347
|
+
|
|
348
|
+
```sh
|
|
349
|
+
waidrop context --last 6h --context-before 30m --max-chars 12000 --format text
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Health check:
|
|
353
|
+
|
|
354
|
+
```sh
|
|
355
|
+
waidrop auth --status --json # exit 0 = ok, 2 = missing scopes
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If the refresh token is ever revoked, re-run `waidrop auth` on the host.
|
|
359
|
+
Rotate the archive key only with a re-encryption plan (key loss means unreadable
|
|
360
|
+
history). Never put access tokens in env files or cron lines; the credentials
|
|
361
|
+
file handles them.
|
|
362
|
+
|
|
363
|
+
## MCP server (optional)
|
|
364
|
+
|
|
365
|
+
Install `waidrop-cli[mcp]` to expose a local, read-only MCP stdio server:
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
pip install "waidrop-cli[mcp]"
|
|
369
|
+
waidrop-mcp --print-setup
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The server exposes `list_chats` and `get_context`, backed by the same archive
|
|
373
|
+
reader as the CLI. It works with any MCP-compatible client and does not contact
|
|
374
|
+
an AI service; selected text is returned only to the connected client. See
|
|
375
|
+
[`docs/mcp.md`](https://github.com/thabiger/waidrop-cli/blob/main/docs/mcp.md).
|
|
376
|
+
|
|
377
|
+
## Security and privacy
|
|
378
|
+
|
|
379
|
+
- The CLI never uploads archive content and has no telemetry. Archives are
|
|
380
|
+
decrypted in memory.
|
|
381
|
+
- The optional MCP server is also local and read-only. It returns selected
|
|
382
|
+
archive text to the connected MCP client over stdio; it does not send content
|
|
383
|
+
to any AI service itself. A cloud-backed client may transmit that data.
|
|
384
|
+
- Dropbox credentials are stored in `~/.config/waidrop/credentials.json`
|
|
385
|
+
(`0600`; parent `0700`). The refresh token grants access to the archive, so
|
|
386
|
+
protect the file.
|
|
387
|
+
- Output files (`--out`, `--media-out`, manifests) are written with private
|
|
388
|
+
permissions. Remove them when they are no longer needed.
|
|
389
|
+
- `waidrop auth --revoke` revokes Dropbox access and removes local
|
|
390
|
+
credentials. If the remote revoke fails, local credentials are kept.
|
|
391
|
+
- Text output escapes terminal control characters. CSV output reduces
|
|
392
|
+
spreadsheet formula injection but remains untrusted data.
|
|
393
|
+
- Report vulnerabilities using the process in [SECURITY.md](SECURITY.md).
|
|
394
|
+
|
|
395
|
+
**Disclaimer:** this project is not affiliated with WhatsApp or Meta. Use it
|
|
396
|
+
only for conversations you are authorized to access. Archiving may violate
|
|
397
|
+
WhatsApp's Terms of Service; use at your own risk. The software is provided
|
|
398
|
+
without warranty under the Apache-2.0 license.
|
|
399
|
+
|
|
400
|
+
## Troubleshooting
|
|
401
|
+
|
|
402
|
+
- **`missing_scope`**: enable the listed scope in the Dropbox App Console →
|
|
403
|
+
Permissions → Submit, then re-authorize. Existing tokens do not gain scopes.
|
|
404
|
+
- **`invalid_access_token`**: token revoked/expired; re-authorize with
|
|
405
|
+
`waidrop auth`.
|
|
406
|
+
- **`chat index could not be decrypted` / `cannot decrypt …`**: wrong archive key
|
|
407
|
+
for the archive.
|
|
408
|
+
- **`plaintext archives are no longer supported`**: re-export the archive with
|
|
409
|
+
encryption enabled.
|
|
410
|
+
- **`archive is encrypted; encryption key required`**: provide `--key` or
|
|
411
|
+
`WAIDROP_KEY`.
|
|
412
|
+
- **`archive format marker is invalid` / `unsupported archive format`**: verify
|
|
413
|
+
`_format.json` or update `waidrop-cli`.
|
|
414
|
+
- **`chat directory is empty`**: nothing has been uploaded yet (connect the
|
|
415
|
+
extension and upload once).
|
|
416
|
+
- **No chats found**: if the archive was created with a different Dropbox app
|
|
417
|
+
key, pass `--dropbox-app-key <old-key>`.
|
|
418
|
+
- **Dates look shifted by a day**: `dt=` is the capturing browser's local date;
|
|
419
|
+
the CLI pads ±1 day and filters by exact `ts`, so this is handled
|
|
420
|
+
automatically — ensure `--tz` matches how you want bounds interpreted.
|
|
421
|
+
|
|
422
|
+
## Limitations
|
|
423
|
+
|
|
424
|
+
- **LID chats** (`@lid`) have no phone number in the jid; number search works only
|
|
425
|
+
when the archive recovered `chatPhone`. Otherwise select by name.
|
|
426
|
+
- **Phone formats** should include the country code; digits are compared.
|
|
427
|
+
- **Names are snapshots** inside message records; `chats` shows the latest name
|
|
428
|
+
from the archive index.
|
|
429
|
+
- **DOM-only records with no `chatId`** are grouped by `chatName` in `context`
|
|
430
|
+
output. Duplicate names cannot be mapped reliably, so they remain separate
|
|
431
|
+
name-only chats with `hash: null`.
|
|
432
|
+
- **Only `stored` media can be exported.** `context --media-out` reports
|
|
433
|
+
`not_downloaded`, `pending`, `view_once`, `too_large` and failed records as
|
|
434
|
+
media messages without files. Capture is passive by default: only media
|
|
435
|
+
WhatsApp already downloaded is stored; `not_downloaded` records have only the
|
|
436
|
+
preview. Video, audio and documents remain metadata/placeholders. Records
|
|
437
|
+
written before media capture cannot be backfilled from the archive.
|
|
438
|
+
- **Exported media files are unencrypted.** `context --media-out` writes
|
|
439
|
+
full-resolution files to `DIR`; keep it private (files are `0600`, directory
|
|
440
|
+
`0700`) and remove it after analysis if it is no longer needed.
|
|
441
|
+
|
|
442
|
+
## Development
|
|
443
|
+
|
|
444
|
+
```sh
|
|
445
|
+
uv sync
|
|
446
|
+
uv run pytest -q # MCP server tests skip without the extra
|
|
447
|
+
uv sync --extra mcp
|
|
448
|
+
uv run pytest -q # includes the MCP server tests
|
|
449
|
+
uv run ruff check .
|
|
450
|
+
uv run ruff format --check .
|
|
451
|
+
uv build
|
|
452
|
+
uvx twine check dist/*
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Tests run against a local fake Dropbox server (stdlib `http.server`) with
|
|
456
|
+
encrypted fixtures and the shared cross-language crypto vector. No Dropbox
|
|
457
|
+
account is needed.
|
|
458
|
+
|
|
459
|
+
The crypto contract is shared with the extension. Do not change the envelope
|
|
460
|
+
magic, version bytes, HKDF salt, or key derivation without updating both
|
|
461
|
+
projects.
|
|
462
|
+
|
|
463
|
+
## Release
|
|
464
|
+
|
|
465
|
+
See [RELEASING.md](RELEASING.md).
|
|
466
|
+
|
|
467
|
+
## Contributing
|
|
468
|
+
|
|
469
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
470
|
+
|
|
471
|
+
## Changelog
|
|
472
|
+
|
|
473
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
474
|
+
|
|
475
|
+
## License
|
|
476
|
+
|
|
477
|
+
Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
waidrop_cli/__init__.py,sha256=T5eM1fyR9uOuX6V2W9XRLSWtPpn3rkluhq9uYhSs-sg,63
|
|
2
|
+
waidrop_cli/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
+
waidrop_cli/archive.py,sha256=MiSL6QFCxFPDGps08oHW_mlPq3DEPduVsf_77xBSqcI,3345
|
|
4
|
+
waidrop_cli/auth.py,sha256=fkYHCouiuPvxLIBRV33rf2b8wNFHZatephxn-UZuUQU,10534
|
|
5
|
+
waidrop_cli/auth_flow.py,sha256=zc26yZ5gAZ9s-hu1Uq-hIhAq-VApoYYcDCYYuZFXd1M,5988
|
|
6
|
+
waidrop_cli/chats.py,sha256=kLYtpBRYsiU-_XmMEEdQ11DrzBIsVlhd5BrEnj1Q3j0,5713
|
|
7
|
+
waidrop_cli/cli.py,sha256=BBe0DWnozOTdNE45a88_j5f3zLTaXvoyZD3MbJCr1kQ,26217
|
|
8
|
+
waidrop_cli/config.py,sha256=3DMDOg4sQLfboX1rMfXkGsTjLCwrAzIWztsHMYatAZw,11453
|
|
9
|
+
waidrop_cli/context.py,sha256=7yUR8KDfX4_Gp78Pud_ItHSouL7B6lRFbW0WXx2Bwwg,11580
|
|
10
|
+
waidrop_cli/crypto.py,sha256=PaMfHF93J_TmiQyYLMWHUty5E6EfnL1gDcy4YSXqwpQ,4492
|
|
11
|
+
waidrop_cli/dropbox.py,sha256=hR-O017eJOzanLfNiO4F_UyrHZI50na7Er85HrrXXmo,8598
|
|
12
|
+
waidrop_cli/env_file.py,sha256=O90RoNW2rAbii79BwWKhwuzqpPedlNpPY9-WAj1Y4mw,4026
|
|
13
|
+
waidrop_cli/errors.py,sha256=mlQAYGfSRTP4NYy6J-HLJ-Z36jdWLG-duxjlFXC9hb8,290
|
|
14
|
+
waidrop_cli/fetch.py,sha256=FETwyw44bXFg-kIO1tiS-Jr7bqh8bKBUTlprW7HjISA,7439
|
|
15
|
+
waidrop_cli/mcp_cli.py,sha256=wsbBTRhXjDwvkboRByaPBb_2uhm7bZx8sCEgRmNb6xw,8647
|
|
16
|
+
waidrop_cli/mcp_config.py,sha256=IJdbdPxHnhi2zJd4_Nyy1yvsXh1V8IN86_xO45E6Wz8,6398
|
|
17
|
+
waidrop_cli/mcp_server.py,sha256=DNsYZdkq5bJWWdcptLUXeK-SB8QOJ3lIs0Ppih5q5aw,12115
|
|
18
|
+
waidrop_cli/media.py,sha256=rEF-QHJGIOt2Xow5y_LLGefbau_5SAJ4_tcSSvzhQzA,6339
|
|
19
|
+
waidrop_cli/output.py,sha256=OINDpBj4-x5XB0231yRZKlSPT8YidBdm7ArE9FCg7wI,4845
|
|
20
|
+
waidrop_cli-1.0.0.dist-info/METADATA,sha256=D8JnhBszj33eJWWZBkT2XyXcUNtc3AMuPzd_eyc2PG0,19757
|
|
21
|
+
waidrop_cli-1.0.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
22
|
+
waidrop_cli-1.0.0.dist-info/entry_points.txt,sha256=mNoPwOhcwMmRiy30pb_on7EoE1xuHxQqyKrx3HLXIVY,91
|
|
23
|
+
waidrop_cli-1.0.0.dist-info/licenses/LICENSE,sha256=g4gAaap9osgEeDUzOcL0DaNkbx8hx4pXI7jrP8UTFp4,11344
|
|
24
|
+
waidrop_cli-1.0.0.dist-info/licenses/NOTICE,sha256=FofoWe_SMwTXTivZALOciXvlepyMCe_BK2_xWEmUCqE,197
|
|
25
|
+
waidrop_cli-1.0.0.dist-info/RECORD,,
|