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.
- gchat_archive-0.2.0/LICENSE +21 -0
- gchat_archive-0.2.0/PKG-INFO +307 -0
- gchat_archive-0.2.0/README.md +274 -0
- gchat_archive-0.2.0/pyproject.toml +61 -0
- gchat_archive-0.2.0/setup.cfg +4 -0
- gchat_archive-0.2.0/src/gchat_archive/__init__.py +3 -0
- gchat_archive-0.2.0/src/gchat_archive/__main__.py +3 -0
- gchat_archive-0.2.0/src/gchat_archive/accounts.py +326 -0
- gchat_archive-0.2.0/src/gchat_archive/api.py +143 -0
- gchat_archive-0.2.0/src/gchat_archive/attachments.py +308 -0
- gchat_archive-0.2.0/src/gchat_archive/auth.py +178 -0
- gchat_archive-0.2.0/src/gchat_archive/backup.py +246 -0
- gchat_archive-0.2.0/src/gchat_archive/browser_source.py +384 -0
- gchat_archive-0.2.0/src/gchat_archive/build.py +464 -0
- gchat_archive-0.2.0/src/gchat_archive/cli.py +372 -0
- gchat_archive-0.2.0/src/gchat_archive/fetch.py +265 -0
- gchat_archive-0.2.0/src/gchat_archive/manage.py +105 -0
- gchat_archive-0.2.0/src/gchat_archive/paths.py +50 -0
- gchat_archive-0.2.0/src/gchat_archive/schedule.py +184 -0
- gchat_archive-0.2.0/src/gchat_archive/serve.py +1629 -0
- gchat_archive-0.2.0/src/gchat_archive/service.py +200 -0
- gchat_archive-0.2.0/src/gchat_archive/textract.py +184 -0
- gchat_archive-0.2.0/src/gchat_archive/web/app.js +1872 -0
- gchat_archive-0.2.0/src/gchat_archive/web/index.html +152 -0
- gchat_archive-0.2.0/src/gchat_archive/web/login.html +176 -0
- gchat_archive-0.2.0/src/gchat_archive/web/setup.html +137 -0
- gchat_archive-0.2.0/src/gchat_archive/web/styles.css +1360 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/PKG-INFO +307 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/SOURCES.txt +36 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/dependency_links.txt +1 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/entry_points.txt +2 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/requires.txt +13 -0
- gchat_archive-0.2.0/src/gchat_archive.egg-info/top_level.txt +1 -0
- gchat_archive-0.2.0/tests/test_backup.py +128 -0
- gchat_archive-0.2.0/tests/test_browser_source.py +71 -0
- gchat_archive-0.2.0/tests/test_build.py +82 -0
- gchat_archive-0.2.0/tests/test_cli.py +36 -0
- 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
|
+
[](https://www.buymeacoffee.com/tu.nguyen.atwork)
|
|
37
|
+
[](https://www.python.org/)
|
|
38
|
+
[](#install)
|
|
39
|
+
[](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
|
+
[](https://www.buymeacoffee.com/tu.nguyen.atwork)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](#install)
|
|
6
|
+
[](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"
|