gchat-archive 0.2.0__tar.gz

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.

Potentially problematic release.


This version of gchat-archive might be problematic. Click here for more details.

Files changed (38) hide show
  1. gchat_archive-0.2.0/LICENSE +21 -0
  2. gchat_archive-0.2.0/PKG-INFO +307 -0
  3. gchat_archive-0.2.0/README.md +274 -0
  4. gchat_archive-0.2.0/pyproject.toml +61 -0
  5. gchat_archive-0.2.0/setup.cfg +4 -0
  6. gchat_archive-0.2.0/src/gchat_archive/__init__.py +3 -0
  7. gchat_archive-0.2.0/src/gchat_archive/__main__.py +3 -0
  8. gchat_archive-0.2.0/src/gchat_archive/accounts.py +326 -0
  9. gchat_archive-0.2.0/src/gchat_archive/api.py +143 -0
  10. gchat_archive-0.2.0/src/gchat_archive/attachments.py +308 -0
  11. gchat_archive-0.2.0/src/gchat_archive/auth.py +178 -0
  12. gchat_archive-0.2.0/src/gchat_archive/backup.py +246 -0
  13. gchat_archive-0.2.0/src/gchat_archive/browser_source.py +384 -0
  14. gchat_archive-0.2.0/src/gchat_archive/build.py +464 -0
  15. gchat_archive-0.2.0/src/gchat_archive/cli.py +372 -0
  16. gchat_archive-0.2.0/src/gchat_archive/fetch.py +265 -0
  17. gchat_archive-0.2.0/src/gchat_archive/manage.py +105 -0
  18. gchat_archive-0.2.0/src/gchat_archive/paths.py +50 -0
  19. gchat_archive-0.2.0/src/gchat_archive/schedule.py +184 -0
  20. gchat_archive-0.2.0/src/gchat_archive/serve.py +1629 -0
  21. gchat_archive-0.2.0/src/gchat_archive/service.py +200 -0
  22. gchat_archive-0.2.0/src/gchat_archive/textract.py +184 -0
  23. gchat_archive-0.2.0/src/gchat_archive/web/app.js +1872 -0
  24. gchat_archive-0.2.0/src/gchat_archive/web/index.html +152 -0
  25. gchat_archive-0.2.0/src/gchat_archive/web/login.html +176 -0
  26. gchat_archive-0.2.0/src/gchat_archive/web/setup.html +137 -0
  27. gchat_archive-0.2.0/src/gchat_archive/web/styles.css +1360 -0
  28. gchat_archive-0.2.0/src/gchat_archive.egg-info/PKG-INFO +307 -0
  29. gchat_archive-0.2.0/src/gchat_archive.egg-info/SOURCES.txt +36 -0
  30. gchat_archive-0.2.0/src/gchat_archive.egg-info/dependency_links.txt +1 -0
  31. gchat_archive-0.2.0/src/gchat_archive.egg-info/entry_points.txt +2 -0
  32. gchat_archive-0.2.0/src/gchat_archive.egg-info/requires.txt +13 -0
  33. gchat_archive-0.2.0/src/gchat_archive.egg-info/top_level.txt +1 -0
  34. gchat_archive-0.2.0/tests/test_backup.py +128 -0
  35. gchat_archive-0.2.0/tests/test_browser_source.py +71 -0
  36. gchat_archive-0.2.0/tests/test_build.py +82 -0
  37. gchat_archive-0.2.0/tests/test_cli.py +36 -0
  38. gchat_archive-0.2.0/tests/test_client_secret.py +114 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gchat-archive contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: gchat-archive
3
+ Version: 0.2.0
4
+ Summary: Keep a local, searchable copy of your Google Chat history
5
+ Author: gchat-archive contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yourname/gchat-archive
8
+ Keywords: google-chat,archive,offline,sqlite,fts5,backup
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Communications :: Chat
18
+ Classifier: Topic :: Database
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: google-auth<3,>=2.0
23
+ Provides-Extra: text
24
+ Requires-Dist: pypdf<6,>=4.0; extra == "text"
25
+ Requires-Dist: python-docx<2,>=1.1; extra == "text"
26
+ Requires-Dist: openpyxl<4,>=3.1; extra == "text"
27
+ Requires-Dist: beautifulsoup4<5,>=4.12; extra == "text"
28
+ Provides-Extra: browser
29
+ Requires-Dist: websocket-client<2,>=1.7; extra == "browser"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest<9,>=8.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # gchat-archive
35
+
36
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-FFDD00?logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/tu.nguyen.atwork)
37
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
38
+ [![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Windows%20%7C%20Linux-555)](#install)
39
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
40
+
41
+ Keep a **local, searchable copy of your Google Chat history** — every space, group chat and DM — in SQLite with full-text search, a browser UI that works offline, and a nightly sync.
42
+
43
+ Google Chat has no export button. This gives you one:
44
+
45
+ - **Every message** in every conversation, incremental sync that resumes where it stopped
46
+ - **Full-text search** over message bodies, sender names, and the text inside shared documents (PDF, DOCX, XLSX, CSV, Google Docs/Sheets/Slides)
47
+ - **Browsable offline** — conversation list, date filters, attachment previews and download
48
+ - **Set up in the browser** — upload your OAuth client, sign in, done; no files to place by hand
49
+ - **Backup & restore from the UI** — one `.tar.gz` rebuilds the archive on a new machine
50
+ - **Optionally a background service** — starts at login, restarts if it stops
51
+ - **Yours only** — listens on `127.0.0.1`, never uploads anything, optional password
52
+ - **Multi-account** — several Google accounts, each with an isolated archive
53
+ - **Prune without losing anything upstream** — delete or exclude conversations and messages locally; Google Chat is never modified
54
+
55
+ macOS · Windows · Linux.
56
+
57
+ Two ways to collect, one archive:
58
+
59
+ | | **API source** (default) | **Browser source** (no setup) |
60
+ |---|---|---|
61
+ | Requires | Google Cloud OAuth client, ~5 min | only your signed-in Chrome |
62
+ | Gets | everything incl. attachments + document text | rendered messages |
63
+ | Stability | stable API | depends on Google's markup |
64
+
65
+ See [docs/BROWSER.md](docs/BROWSER.md) for the browser source.
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ Requires **Python 3.10+**.
72
+
73
+ ### macOS / Linux
74
+
75
+ ```bash
76
+ pipx install "gchat-archive[text]" # recommended: isolated, on your PATH
77
+ # or
78
+ python3 -m pip install --user "gchat-archive[text]"
79
+ ```
80
+
81
+ ### Windows (PowerShell)
82
+
83
+ ```powershell
84
+ py -m pip install --user "pipx"
85
+ py -m pipx install "gchat-archive[text]"
86
+ ```
87
+
88
+ `[text]` adds the document parsers (pypdf, python-docx, openpyxl). Without it everything still works — search just won't look inside attachments.
89
+
90
+ ### From a checkout
91
+
92
+ ```bash
93
+ git clone https://github.com/yourname/gchat-archive.git
94
+ cd gchat-archive
95
+ python3 -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
96
+ pip install -e ".[text,dev]"
97
+ pytest
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Set up (in the browser)
103
+
104
+ ```bash
105
+ gchat-archive serve --open # → http://127.0.0.1:8765
106
+ ```
107
+
108
+ A fresh install opens **`/setup`** by itself:
109
+
110
+ 1. **Create the OAuth client.** The page links every step — new project → enable **Google Chat API**
111
+ and **Google Drive API** → OAuth consent screen (*External*, add yourself as a test user, four
112
+ scopes) → **Credentials → Create credentials → OAuth client ID → Desktop app** → **Download JSON**.
113
+ Free, no billing account, about five minutes.
114
+ 2. **Upload that JSON** — drag and drop or click to choose. It is validated: a *web* client is
115
+ rejected with a hint to recreate it as *Desktop app*, broken JSON is named as such. Stored
116
+ owner-only at `~/.gchat-archive/client_secret.json`. Replace it any time from
117
+ Settings → Google accounts.
118
+ 3. **Continue to Google sign-in** and pick the account to archive. Add more accounts later from the
119
+ same panel.
120
+ 4. **Sync.** The first run downloads everything (~40 min for a 240k-message archive). Watch it in the
121
+ UI or run `gchat-archive sync`.
122
+ 5. **Set a password** (Settings → Security) if the machine is shared.
123
+
124
+ Prefer files to clicks? Put the JSON at `~/.gchat-archive/client_secret.json` (or point
125
+ `GCHAT_CLIENT_SECRET` at it) and run `gchat-archive login` — same flow. Full walkthrough with
126
+ troubleshooting: **[docs/SETUP.md](docs/SETUP.md)**.
127
+
128
+ ---
129
+
130
+ ## Move to a new machine
131
+
132
+ Everything travels in one file.
133
+
134
+ **Old machine** — Settings → **Backup & restore** → pick a size:
135
+
136
+ | Kind | Contains | Typical |
137
+ |---|---|---|
138
+ | Settings only | OAuth client, account registry, password hash, rules, sync watermarks | ~40 KB |
139
+ | **Archive + database** | the above + `chats.sqlite` (all messages + search index) | ~150 MB compressed |
140
+ | Everything | the above + `raw/` messages and downloaded attachments | ~3 GB |
141
+
142
+ **New machine:**
143
+
144
+ ```bash
145
+ pipx install "gchat-archive[text]"
146
+ gchat-archive serve --open
147
+ ```
148
+
149
+ Settings → **Backup & restore** → choose the file. An empty install installs it directly; an existing
150
+ archive asks first (showing the backup's date, kind and message count) and copies the current settings
151
+ to `pre-restore-<timestamp>/`. Reload, enter the same password — history, rules and Google credentials
152
+ are all there, so the next `sync` continues where the old machine stopped.
153
+
154
+ CLI equivalents:
155
+
156
+ ```bash
157
+ gchat-archive backup --kind archive --out ~/Desktop/gca.tar.gz # old machine
158
+ gchat-archive restore ~/Desktop/gca.tar.gz # new machine
159
+ ```
160
+
161
+ Backups **contain credentials** (client secret, tokens, password hash) — that is what makes a restore
162
+ self-sufficient, so store the file somewhere private.
163
+
164
+ ---
165
+
166
+ ## Run it in the background (optional)
167
+
168
+ Settings → **Run at login** installs the reader as a per-user service, so the archive is available
169
+ whenever you are logged in:
170
+
171
+ | Platform | Mechanism | Where |
172
+ |---|---|---|
173
+ | macOS | LaunchAgent (`RunAtLoad`, `KeepAlive`) | `~/Library/LaunchAgents/com.gchat-archive.serve.plist` |
174
+ | Linux | `systemd --user` unit (`Restart=on-failure`) | `~/.config/systemd/user/gchat-archive.service` |
175
+ | Windows | Task Scheduler, at logon | task `gchat-archive serve` |
176
+
177
+ ```bash
178
+ gchat-archive service install [--port 8765]
179
+ gchat-archive service status
180
+ gchat-archive service uninstall
181
+ ```
182
+
183
+ Logs land in `~/.gchat-archive/service.log` and `service.err.log`. Stop a hand-started instance before
184
+ installing, or the service has no port to bind. On Linux, `loginctl enable-linger $USER` keeps it
185
+ running while you are logged out.
186
+
187
+ The nightly sync is a separate, also-optional schedule:
188
+
189
+ ```bash
190
+ gchat-archive schedule set "0 5 * * *" # crontab on macOS/Linux, Task Scheduler on Windows
191
+ gchat-archive schedule status
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Commands
197
+
198
+ | Command | What it does |
199
+ |---|---|
200
+ | `gchat-archive serve` | Reader UI on <http://127.0.0.1:8765> (`--port`, `--open`) |
201
+ | `gchat-archive login` | Add a Google account (opens `/setup` until a client is uploaded) |
202
+ | `gchat-archive sync` | Fetch new messages → attachments → rebuild the database |
203
+ | `gchat-archive sync --full` | Re-pull every conversation from scratch |
204
+ | `gchat-archive sync --check-all` | Also re-check conversations that look idle (catches edits/deletes) |
205
+ | `gchat-archive build` | Rebuild `chats.sqlite` from downloaded messages |
206
+ | `gchat-archive attachments download [N]` / `stats` | Manage attachment files |
207
+ | `gchat-archive accounts list\|use\|remove` | Manage accounts |
208
+ | `gchat-archive backup [--kind config\|archive\|full]` | Write a restorable `.tar.gz` |
209
+ | `gchat-archive restore FILE [--force]` | Install a backup (config, database, rules) |
210
+ | `gchat-archive service install\|status\|uninstall` | Run the reader at login |
211
+ | `gchat-archive schedule set\|pause\|resume\|status` | Nightly sync |
212
+ | `gchat-archive doctor` | Check the install: paths, token, optional parsers |
213
+ | `gchat-archive browser launch\|calibrate\|harvest` | Harvest through a signed-in Chrome instead |
214
+
215
+ `--data-dir PATH` (or `GCHAT_ARCHIVE_HOME`) moves the archive anywhere.
216
+
217
+ ---
218
+
219
+ ## What gets stored
220
+
221
+ ```
222
+ ~/.gchat-archive/
223
+ ├── client_secret.json your OAuth client (owner-only)
224
+ ├── auth.json archive password hash (owner-only)
225
+ ├── accounts.json which accounts exist, which is active
226
+ ├── tokens/<slug>.json OAuth tokens, owner-only
227
+ └── accounts/<slug>/
228
+ ├── raw/<space>.jsonl append-only API responses, one JSON per line
229
+ ├── raw/<space>.meta.json conversation metadata
230
+ ├── state.json per-conversation watermarks
231
+ ├── chats.sqlite the searchable database (a projection of raw)
232
+ ├── attachments/ downloaded files
233
+ ├── attachments.jsonl what was downloaded
234
+ ├── attachments_text.jsonl text extracted from those files
235
+ └── archive_manage.json your delete/exclude rules
236
+ ```
237
+
238
+ The database is **disposable** — it is rebuilt from `raw/` minus `archive_manage.json` on every sync.
239
+ Deleting a message or conversation in the UI records it in the rules file, so a rebuild cannot
240
+ resurrect it, and removing the rule brings it back.
241
+
242
+ Everything stays on your machine. The only network traffic is to Google's APIs.
243
+
244
+ ---
245
+
246
+ ## Notes and limits
247
+
248
+ - **Scopes**: `chat.messages`, `chat.spaces.readonly`, `drive`, `gmail.readonly`. `drive` is needed to export Google Docs/Sheets; `gmail.readonly` only to learn your own address.
249
+ - **Edits and deletions upstream** are picked up when a conversation is re-read. Idle conversations are skipped for speed — run `sync --check-all` weekly if you care about old edits.
250
+ - **Images** are opt-in (Settings per account): ~1.7 GB for a 240k-message archive. They are stored, not OCR'd.
251
+ - **Video and other binaries** are skipped; their filenames are still indexed.
252
+ - **The nightly sync** needs the machine awake; a laptop that sleeps through 05:00 simply syncs on the next run.
253
+ - This is a personal archive tool, not a compliance/retention system.
254
+
255
+ ---
256
+
257
+ ## Security
258
+
259
+ - Binds to `127.0.0.1` by default — not reachable from your network
260
+ - Optional password (Settings → Security): PBKDF2 hash, in-memory sessions, password reset by email to the linked account
261
+ - Credentials, tokens and backups are stored owner-only; `.gitignore` keeps them out of git
262
+ - Revoke access any time at <https://myaccount.google.com/permissions>
263
+
264
+ ---
265
+
266
+ ## Browser source (no OAuth)
267
+
268
+ ```bash
269
+ pip install "gchat-archive[browser]"
270
+ # quit Chrome, then:
271
+ gchat-archive browser launch --profile "Profile 21"
272
+ gchat-archive browser calibrate # open a conversation first
273
+ gchat-archive browser harvest --conversation "project-room"
274
+ gchat-archive build && gchat-archive serve
275
+ ```
276
+
277
+ It drives your signed-in Chrome over the DevTools protocol, scrolls the open conversation, and writes
278
+ the same `raw/*.jsonl` the API source writes. No Google Cloud project, no scopes, no token.
279
+ Experimental: it depends on the rendered DOM, which is why `calibrate` exists — it reports whether the
280
+ current markup is understood. Attachments are not collected in this mode. Details and limits:
281
+ [docs/BROWSER.md](docs/BROWSER.md).
282
+
283
+ ## Development
284
+
285
+ ```bash
286
+ pip install -e ".[text,dev]"
287
+ pytest # offline: build, FTS, rules, backup/restore, CLI, endpoints
288
+ ```
289
+
290
+ Layout: `src/gchat_archive/` — `api` (Chat REST), `fetch` (incremental sync), `build` (raw → SQLite/FTS5), `attachments`, `textract`, `manage` (rules), `accounts`, `auth`, `serve` (stdlib HTTP + UI), `backup`, `service`, `schedule`, `cli`. The browser app is plain HTML/CSS/JS in `src/gchat_archive/web/`, no build step.
291
+
292
+ Installing from a checkout into another location is a plain reinstall — the UI ships inside the wheel,
293
+ so re-run the install after changing anything under `web/`.
294
+
295
+ ## Support
296
+
297
+ If this saved you an afternoon of scrolling, you can buy me a coffee:
298
+
299
+ <a href="https://www.buymeacoffee.com/tu.nguyen.atwork" target="_blank">
300
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="44" width="159">
301
+ </a>
302
+
303
+ No obligation — starring the repo, filing a clear bug report or sending a PR helps just as much.
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,274 @@
1
+ # gchat-archive
2
+
3
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-FFDD00?logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/tu.nguyen.atwork)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
5
+ [![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Windows%20%7C%20Linux-555)](#install)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ Keep a **local, searchable copy of your Google Chat history** — every space, group chat and DM — in SQLite with full-text search, a browser UI that works offline, and a nightly sync.
9
+
10
+ Google Chat has no export button. This gives you one:
11
+
12
+ - **Every message** in every conversation, incremental sync that resumes where it stopped
13
+ - **Full-text search** over message bodies, sender names, and the text inside shared documents (PDF, DOCX, XLSX, CSV, Google Docs/Sheets/Slides)
14
+ - **Browsable offline** — conversation list, date filters, attachment previews and download
15
+ - **Set up in the browser** — upload your OAuth client, sign in, done; no files to place by hand
16
+ - **Backup & restore from the UI** — one `.tar.gz` rebuilds the archive on a new machine
17
+ - **Optionally a background service** — starts at login, restarts if it stops
18
+ - **Yours only** — listens on `127.0.0.1`, never uploads anything, optional password
19
+ - **Multi-account** — several Google accounts, each with an isolated archive
20
+ - **Prune without losing anything upstream** — delete or exclude conversations and messages locally; Google Chat is never modified
21
+
22
+ macOS · Windows · Linux.
23
+
24
+ Two ways to collect, one archive:
25
+
26
+ | | **API source** (default) | **Browser source** (no setup) |
27
+ |---|---|---|
28
+ | Requires | Google Cloud OAuth client, ~5 min | only your signed-in Chrome |
29
+ | Gets | everything incl. attachments + document text | rendered messages |
30
+ | Stability | stable API | depends on Google's markup |
31
+
32
+ See [docs/BROWSER.md](docs/BROWSER.md) for the browser source.
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ Requires **Python 3.10+**.
39
+
40
+ ### macOS / Linux
41
+
42
+ ```bash
43
+ pipx install "gchat-archive[text]" # recommended: isolated, on your PATH
44
+ # or
45
+ python3 -m pip install --user "gchat-archive[text]"
46
+ ```
47
+
48
+ ### Windows (PowerShell)
49
+
50
+ ```powershell
51
+ py -m pip install --user "pipx"
52
+ py -m pipx install "gchat-archive[text]"
53
+ ```
54
+
55
+ `[text]` adds the document parsers (pypdf, python-docx, openpyxl). Without it everything still works — search just won't look inside attachments.
56
+
57
+ ### From a checkout
58
+
59
+ ```bash
60
+ git clone https://github.com/yourname/gchat-archive.git
61
+ cd gchat-archive
62
+ python3 -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
63
+ pip install -e ".[text,dev]"
64
+ pytest
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Set up (in the browser)
70
+
71
+ ```bash
72
+ gchat-archive serve --open # → http://127.0.0.1:8765
73
+ ```
74
+
75
+ A fresh install opens **`/setup`** by itself:
76
+
77
+ 1. **Create the OAuth client.** The page links every step — new project → enable **Google Chat API**
78
+ and **Google Drive API** → OAuth consent screen (*External*, add yourself as a test user, four
79
+ scopes) → **Credentials → Create credentials → OAuth client ID → Desktop app** → **Download JSON**.
80
+ Free, no billing account, about five minutes.
81
+ 2. **Upload that JSON** — drag and drop or click to choose. It is validated: a *web* client is
82
+ rejected with a hint to recreate it as *Desktop app*, broken JSON is named as such. Stored
83
+ owner-only at `~/.gchat-archive/client_secret.json`. Replace it any time from
84
+ Settings → Google accounts.
85
+ 3. **Continue to Google sign-in** and pick the account to archive. Add more accounts later from the
86
+ same panel.
87
+ 4. **Sync.** The first run downloads everything (~40 min for a 240k-message archive). Watch it in the
88
+ UI or run `gchat-archive sync`.
89
+ 5. **Set a password** (Settings → Security) if the machine is shared.
90
+
91
+ Prefer files to clicks? Put the JSON at `~/.gchat-archive/client_secret.json` (or point
92
+ `GCHAT_CLIENT_SECRET` at it) and run `gchat-archive login` — same flow. Full walkthrough with
93
+ troubleshooting: **[docs/SETUP.md](docs/SETUP.md)**.
94
+
95
+ ---
96
+
97
+ ## Move to a new machine
98
+
99
+ Everything travels in one file.
100
+
101
+ **Old machine** — Settings → **Backup & restore** → pick a size:
102
+
103
+ | Kind | Contains | Typical |
104
+ |---|---|---|
105
+ | Settings only | OAuth client, account registry, password hash, rules, sync watermarks | ~40 KB |
106
+ | **Archive + database** | the above + `chats.sqlite` (all messages + search index) | ~150 MB compressed |
107
+ | Everything | the above + `raw/` messages and downloaded attachments | ~3 GB |
108
+
109
+ **New machine:**
110
+
111
+ ```bash
112
+ pipx install "gchat-archive[text]"
113
+ gchat-archive serve --open
114
+ ```
115
+
116
+ Settings → **Backup & restore** → choose the file. An empty install installs it directly; an existing
117
+ archive asks first (showing the backup's date, kind and message count) and copies the current settings
118
+ to `pre-restore-<timestamp>/`. Reload, enter the same password — history, rules and Google credentials
119
+ are all there, so the next `sync` continues where the old machine stopped.
120
+
121
+ CLI equivalents:
122
+
123
+ ```bash
124
+ gchat-archive backup --kind archive --out ~/Desktop/gca.tar.gz # old machine
125
+ gchat-archive restore ~/Desktop/gca.tar.gz # new machine
126
+ ```
127
+
128
+ Backups **contain credentials** (client secret, tokens, password hash) — that is what makes a restore
129
+ self-sufficient, so store the file somewhere private.
130
+
131
+ ---
132
+
133
+ ## Run it in the background (optional)
134
+
135
+ Settings → **Run at login** installs the reader as a per-user service, so the archive is available
136
+ whenever you are logged in:
137
+
138
+ | Platform | Mechanism | Where |
139
+ |---|---|---|
140
+ | macOS | LaunchAgent (`RunAtLoad`, `KeepAlive`) | `~/Library/LaunchAgents/com.gchat-archive.serve.plist` |
141
+ | Linux | `systemd --user` unit (`Restart=on-failure`) | `~/.config/systemd/user/gchat-archive.service` |
142
+ | Windows | Task Scheduler, at logon | task `gchat-archive serve` |
143
+
144
+ ```bash
145
+ gchat-archive service install [--port 8765]
146
+ gchat-archive service status
147
+ gchat-archive service uninstall
148
+ ```
149
+
150
+ Logs land in `~/.gchat-archive/service.log` and `service.err.log`. Stop a hand-started instance before
151
+ installing, or the service has no port to bind. On Linux, `loginctl enable-linger $USER` keeps it
152
+ running while you are logged out.
153
+
154
+ The nightly sync is a separate, also-optional schedule:
155
+
156
+ ```bash
157
+ gchat-archive schedule set "0 5 * * *" # crontab on macOS/Linux, Task Scheduler on Windows
158
+ gchat-archive schedule status
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Commands
164
+
165
+ | Command | What it does |
166
+ |---|---|
167
+ | `gchat-archive serve` | Reader UI on <http://127.0.0.1:8765> (`--port`, `--open`) |
168
+ | `gchat-archive login` | Add a Google account (opens `/setup` until a client is uploaded) |
169
+ | `gchat-archive sync` | Fetch new messages → attachments → rebuild the database |
170
+ | `gchat-archive sync --full` | Re-pull every conversation from scratch |
171
+ | `gchat-archive sync --check-all` | Also re-check conversations that look idle (catches edits/deletes) |
172
+ | `gchat-archive build` | Rebuild `chats.sqlite` from downloaded messages |
173
+ | `gchat-archive attachments download [N]` / `stats` | Manage attachment files |
174
+ | `gchat-archive accounts list\|use\|remove` | Manage accounts |
175
+ | `gchat-archive backup [--kind config\|archive\|full]` | Write a restorable `.tar.gz` |
176
+ | `gchat-archive restore FILE [--force]` | Install a backup (config, database, rules) |
177
+ | `gchat-archive service install\|status\|uninstall` | Run the reader at login |
178
+ | `gchat-archive schedule set\|pause\|resume\|status` | Nightly sync |
179
+ | `gchat-archive doctor` | Check the install: paths, token, optional parsers |
180
+ | `gchat-archive browser launch\|calibrate\|harvest` | Harvest through a signed-in Chrome instead |
181
+
182
+ `--data-dir PATH` (or `GCHAT_ARCHIVE_HOME`) moves the archive anywhere.
183
+
184
+ ---
185
+
186
+ ## What gets stored
187
+
188
+ ```
189
+ ~/.gchat-archive/
190
+ ├── client_secret.json your OAuth client (owner-only)
191
+ ├── auth.json archive password hash (owner-only)
192
+ ├── accounts.json which accounts exist, which is active
193
+ ├── tokens/<slug>.json OAuth tokens, owner-only
194
+ └── accounts/<slug>/
195
+ ├── raw/<space>.jsonl append-only API responses, one JSON per line
196
+ ├── raw/<space>.meta.json conversation metadata
197
+ ├── state.json per-conversation watermarks
198
+ ├── chats.sqlite the searchable database (a projection of raw)
199
+ ├── attachments/ downloaded files
200
+ ├── attachments.jsonl what was downloaded
201
+ ├── attachments_text.jsonl text extracted from those files
202
+ └── archive_manage.json your delete/exclude rules
203
+ ```
204
+
205
+ The database is **disposable** — it is rebuilt from `raw/` minus `archive_manage.json` on every sync.
206
+ Deleting a message or conversation in the UI records it in the rules file, so a rebuild cannot
207
+ resurrect it, and removing the rule brings it back.
208
+
209
+ Everything stays on your machine. The only network traffic is to Google's APIs.
210
+
211
+ ---
212
+
213
+ ## Notes and limits
214
+
215
+ - **Scopes**: `chat.messages`, `chat.spaces.readonly`, `drive`, `gmail.readonly`. `drive` is needed to export Google Docs/Sheets; `gmail.readonly` only to learn your own address.
216
+ - **Edits and deletions upstream** are picked up when a conversation is re-read. Idle conversations are skipped for speed — run `sync --check-all` weekly if you care about old edits.
217
+ - **Images** are opt-in (Settings per account): ~1.7 GB for a 240k-message archive. They are stored, not OCR'd.
218
+ - **Video and other binaries** are skipped; their filenames are still indexed.
219
+ - **The nightly sync** needs the machine awake; a laptop that sleeps through 05:00 simply syncs on the next run.
220
+ - This is a personal archive tool, not a compliance/retention system.
221
+
222
+ ---
223
+
224
+ ## Security
225
+
226
+ - Binds to `127.0.0.1` by default — not reachable from your network
227
+ - Optional password (Settings → Security): PBKDF2 hash, in-memory sessions, password reset by email to the linked account
228
+ - Credentials, tokens and backups are stored owner-only; `.gitignore` keeps them out of git
229
+ - Revoke access any time at <https://myaccount.google.com/permissions>
230
+
231
+ ---
232
+
233
+ ## Browser source (no OAuth)
234
+
235
+ ```bash
236
+ pip install "gchat-archive[browser]"
237
+ # quit Chrome, then:
238
+ gchat-archive browser launch --profile "Profile 21"
239
+ gchat-archive browser calibrate # open a conversation first
240
+ gchat-archive browser harvest --conversation "project-room"
241
+ gchat-archive build && gchat-archive serve
242
+ ```
243
+
244
+ It drives your signed-in Chrome over the DevTools protocol, scrolls the open conversation, and writes
245
+ the same `raw/*.jsonl` the API source writes. No Google Cloud project, no scopes, no token.
246
+ Experimental: it depends on the rendered DOM, which is why `calibrate` exists — it reports whether the
247
+ current markup is understood. Attachments are not collected in this mode. Details and limits:
248
+ [docs/BROWSER.md](docs/BROWSER.md).
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ pip install -e ".[text,dev]"
254
+ pytest # offline: build, FTS, rules, backup/restore, CLI, endpoints
255
+ ```
256
+
257
+ Layout: `src/gchat_archive/` — `api` (Chat REST), `fetch` (incremental sync), `build` (raw → SQLite/FTS5), `attachments`, `textract`, `manage` (rules), `accounts`, `auth`, `serve` (stdlib HTTP + UI), `backup`, `service`, `schedule`, `cli`. The browser app is plain HTML/CSS/JS in `src/gchat_archive/web/`, no build step.
258
+
259
+ Installing from a checkout into another location is a plain reinstall — the UI ships inside the wheel,
260
+ so re-run the install after changing anything under `web/`.
261
+
262
+ ## Support
263
+
264
+ If this saved you an afternoon of scrolling, you can buy me a coffee:
265
+
266
+ <a href="https://www.buymeacoffee.com/tu.nguyen.atwork" target="_blank">
267
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="44" width="159">
268
+ </a>
269
+
270
+ No obligation — starring the repo, filing a clear bug report or sending a PR helps just as much.
271
+
272
+ ## License
273
+
274
+ MIT
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gchat-archive"
7
+ version = "0.2.0"
8
+ description = "Keep a local, searchable copy of your Google Chat history"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "gchat-archive contributors" }]
13
+ keywords = ["google-chat", "archive", "offline", "sqlite", "fts5", "backup"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Topic :: Communications :: Chat",
24
+ "Topic :: Database",
25
+ ]
26
+ dependencies = [
27
+ "google-auth>=2.0,<3",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ text = [
32
+ "pypdf>=4.0,<6",
33
+ "python-docx>=1.1,<2",
34
+ "openpyxl>=3.1,<4",
35
+ "beautifulsoup4>=4.12,<5",
36
+ ]
37
+ browser = [
38
+ "websocket-client>=1.7,<2",
39
+ ]
40
+ dev = [
41
+ "pytest>=8.0,<9",
42
+ ]
43
+
44
+ [project.scripts]
45
+ gchat-archive = "gchat_archive.cli:main"
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/yourname/gchat-archive"
49
+
50
+ [tool.setuptools]
51
+ package-dir = { "" = "src" }
52
+
53
+ [tool.setuptools.packages.find]
54
+ where = ["src"]
55
+
56
+ [tool.setuptools.package-data]
57
+ gchat_archive = ["web/*"]
58
+
59
+ [tool.pytest.ini_options]
60
+ testpaths = ["tests"]
61
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Local, searchable archive of Google Chat history."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())