gmail-streamer 0.2.2__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.
- gmail_streamer-0.2.2/.github/dependabot.yml +6 -0
- gmail_streamer-0.2.2/.github/workflows/ci.yml +11 -0
- gmail_streamer-0.2.2/.github/workflows/release.yml +73 -0
- gmail_streamer-0.2.2/.gitignore +22 -0
- gmail_streamer-0.2.2/.vscode/settings.json +22 -0
- gmail_streamer-0.2.2/CLAUDE.md +48 -0
- gmail_streamer-0.2.2/LICENSE +21 -0
- gmail_streamer-0.2.2/PKG-INFO +132 -0
- gmail_streamer-0.2.2/README.md +104 -0
- gmail_streamer-0.2.2/gmail_streamer/_build_info.py +1 -0
- gmail_streamer-0.2.2/hatch_build.py +24 -0
- gmail_streamer-0.2.2/logo.png +0 -0
- gmail_streamer-0.2.2/profiles/example/config.yaml +3 -0
- gmail_streamer-0.2.2/pyproject.toml +45 -0
- gmail_streamer-0.2.2/src/gmail_streamer/__init__.py +1 -0
- gmail_streamer-0.2.2/src/gmail_streamer/auth.py +33 -0
- gmail_streamer-0.2.2/src/gmail_streamer/cli.py +145 -0
- gmail_streamer-0.2.2/src/gmail_streamer/config.py +28 -0
- gmail_streamer-0.2.2/src/gmail_streamer/gmail_client.py +68 -0
- gmail_streamer-0.2.2/src/gmail_streamer/paths.py +42 -0
- gmail_streamer-0.2.2/src/gmail_streamer/storage.py +50 -0
- gmail_streamer-0.2.2/uv.lock +509 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- pyproject.toml
|
|
8
|
+
- src/gmail_streamer/__init__.py
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 10
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: astral-sh/setup-uv@v5
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
cache-dependency-glob: "uv.lock"
|
|
28
|
+
- run: uv sync
|
|
29
|
+
- run: |
|
|
30
|
+
exit_code=0
|
|
31
|
+
uv run --with pytest pytest || exit_code=$?
|
|
32
|
+
if [ $exit_code -eq 5 ]; then
|
|
33
|
+
echo "No tests collected — skipping"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
exit $exit_code
|
|
37
|
+
|
|
38
|
+
pii-scan:
|
|
39
|
+
uses: tsilva/.github/.github/workflows/pii-scan.yml@main
|
|
40
|
+
|
|
41
|
+
publish-pypi:
|
|
42
|
+
needs: [test, pii-scan]
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
timeout-minutes: 15
|
|
45
|
+
environment: pypi
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
with:
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
|
|
51
|
+
- name: Check version
|
|
52
|
+
id: check
|
|
53
|
+
uses: tsilva/.github/.github/actions/check-version@main
|
|
54
|
+
|
|
55
|
+
- name: Setup uv
|
|
56
|
+
if: steps.check.outputs.tag_exists == 'false'
|
|
57
|
+
uses: astral-sh/setup-uv@v5
|
|
58
|
+
with:
|
|
59
|
+
enable-cache: true
|
|
60
|
+
|
|
61
|
+
- name: Build package
|
|
62
|
+
if: steps.check.outputs.tag_exists == 'false'
|
|
63
|
+
run: uv build
|
|
64
|
+
|
|
65
|
+
- name: Create release
|
|
66
|
+
if: steps.check.outputs.tag_exists == 'false'
|
|
67
|
+
env:
|
|
68
|
+
GH_TOKEN: ${{ github.token }}
|
|
69
|
+
run: gh release create ${{ steps.check.outputs.tag }} dist/* --generate-notes
|
|
70
|
+
|
|
71
|
+
- name: Publish to PyPI
|
|
72
|
+
if: steps.check.outputs.tag_exists == 'false'
|
|
73
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.egg-info/
|
|
3
|
+
dist/
|
|
4
|
+
build/
|
|
5
|
+
src/gmail_streamer/_build_info.py
|
|
6
|
+
*.pyc
|
|
7
|
+
.venv/
|
|
8
|
+
profiles/*/
|
|
9
|
+
!profiles/example/
|
|
10
|
+
profiles/example/token.json
|
|
11
|
+
profiles/example/state.json
|
|
12
|
+
profiles/example/credentials.json
|
|
13
|
+
|
|
14
|
+
logs/
|
|
15
|
+
|
|
16
|
+
# Managed by tsilva/.github
|
|
17
|
+
# Do not remove - synced automatically
|
|
18
|
+
.claude/
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
.DS_Store
|
|
22
|
+
node_modules/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workbench.colorCustomizations": {
|
|
3
|
+
"activityBar.activeBackground": "#f8b688",
|
|
4
|
+
"activityBar.background": "#f8b688",
|
|
5
|
+
"activityBar.foreground": "#15202b",
|
|
6
|
+
"activityBar.inactiveForeground": "#15202b99",
|
|
7
|
+
"activityBarBadge.background": "#099743",
|
|
8
|
+
"activityBarBadge.foreground": "#e7e7e7",
|
|
9
|
+
"commandCenter.border": "#15202b99",
|
|
10
|
+
"sash.hoverBorder": "#f8b688",
|
|
11
|
+
"statusBar.background": "#f59858",
|
|
12
|
+
"statusBar.foreground": "#15202b",
|
|
13
|
+
"statusBarItem.hoverBackground": "#f27a28",
|
|
14
|
+
"statusBarItem.remoteBackground": "#f59858",
|
|
15
|
+
"statusBarItem.remoteForeground": "#15202b",
|
|
16
|
+
"titleBar.activeBackground": "#f59858",
|
|
17
|
+
"titleBar.activeForeground": "#15202b",
|
|
18
|
+
"titleBar.inactiveBackground": "#f5985899",
|
|
19
|
+
"titleBar.inactiveForeground": "#15202b99"
|
|
20
|
+
},
|
|
21
|
+
"peacock.color": "#f59858"
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
gmail-streamer is a Python CLI tool that downloads Gmail messages matching configurable filters via OAuth2, organized by profiles.
|
|
6
|
+
|
|
7
|
+
## Build & Run
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync # Install dependencies
|
|
11
|
+
gmail-streamer run <profile> # Download messages
|
|
12
|
+
gmail-streamer --profile-dir /path run <profile> # Custom profile dir
|
|
13
|
+
gmail-streamer profiles list # List available profiles
|
|
14
|
+
gmail-streamer profiles init <name> # Scaffold new profile
|
|
15
|
+
gmail-streamer profiles show <name> # Show profile config
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
- `src/gmail_streamer/cli.py` — Click CLI entry point (group with `run` and `profiles` subcommands)
|
|
21
|
+
- `src/gmail_streamer/paths.py` — Profile directory resolution and discovery
|
|
22
|
+
- `src/gmail_streamer/config.py` — Loads and validates `config.yaml` into a `ProfileConfig` dataclass
|
|
23
|
+
- `src/gmail_streamer/auth.py` — OAuth2 flow using google-auth-oauthlib, token caching
|
|
24
|
+
- `src/gmail_streamer/gmail_client.py` — Gmail API wrapper: search, fetch raw messages, fetch attachments
|
|
25
|
+
- `src/gmail_streamer/storage.py` — Saves `.eml` files and attachment files to disk
|
|
26
|
+
|
|
27
|
+
## Profile Resolution
|
|
28
|
+
|
|
29
|
+
Profiles directory is resolved in this order:
|
|
30
|
+
1. `--profile-dir` flag or `GMAIL_STREAMER_PROFILE_DIR` env var
|
|
31
|
+
2. `./profiles/` in current working directory (if it exists)
|
|
32
|
+
3. `~/.gmail-streamer/profiles/` (fallback)
|
|
33
|
+
|
|
34
|
+
The `profile` argument to `run` can be a name (looked up in the profiles dir) or a path to an existing directory.
|
|
35
|
+
|
|
36
|
+
## Profile Structure
|
|
37
|
+
|
|
38
|
+
Each profile lives in its own directory with:
|
|
39
|
+
- `config.yaml` — filter query, target directory, mode (full/attachments_only)
|
|
40
|
+
- `credentials.json` — user-provided OAuth client credentials
|
|
41
|
+
- `token.json` — auto-generated after first OAuth flow
|
|
42
|
+
|
|
43
|
+
## Key Conventions
|
|
44
|
+
|
|
45
|
+
- Python 3.12+, uses hatchling build backend with uv
|
|
46
|
+
- No tests yet
|
|
47
|
+
- Sensitive files (token.json, credentials.json) are gitignored
|
|
48
|
+
- README.md must be kept up to date with any significant project changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tiago Silva
|
|
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,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gmail-streamer
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: CLI tool to download Gmail messages matching configurable filters via OAuth2
|
|
5
|
+
Project-URL: Homepage, https://github.com/tsilva/gmail-streamer
|
|
6
|
+
Project-URL: Repository, https://github.com/tsilva/gmail-streamer
|
|
7
|
+
Project-URL: Issues, https://github.com/tsilva/gmail-streamer/issues
|
|
8
|
+
Author: Tiago Silva
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: attachments,backup,cli,download,email,gmail,google,oauth2
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications :: Email
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: click
|
|
23
|
+
Requires-Dist: google-api-python-client
|
|
24
|
+
Requires-Dist: google-auth-httplib2
|
|
25
|
+
Requires-Dist: google-auth-oauthlib
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<div align="center">
|
|
30
|
+
<img src="logo.png" alt="gmail-streamer" width="512"/>
|
|
31
|
+
|
|
32
|
+
[](https://python.org)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
**📧 Download Gmail messages matching your filters to local files 📥**
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
## ✨ Features
|
|
40
|
+
|
|
41
|
+
- **🗂️ Profile-based configuration** — run multiple independent download profiles, each with its own filters, credentials, and output directory
|
|
42
|
+
- **🔐 OAuth2 authentication** — secure Google sign-in with automatic token caching
|
|
43
|
+
- **📧 Full message download** — save complete `.eml` files for archival
|
|
44
|
+
- **📎 Attachments-only mode** — grab just the attachments, skip the rest
|
|
45
|
+
- **🧠 Incremental downloads** — remembers what's already been downloaded, no duplicates across runs
|
|
46
|
+
- **🔍 Gmail search filters** — use any Gmail search query (`from:`, `has:attachment`, `after:`, label filters, etc.)
|
|
47
|
+
- **🏠 Works from anywhere** — install globally with `uv` and run from any directory
|
|
48
|
+
|
|
49
|
+
## 🚀 Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/tsilva/gmail-streamer.git
|
|
55
|
+
cd gmail-streamer
|
|
56
|
+
uv tool install . --force --no-cache
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Create a profile
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gmail-streamer profiles init my-profile
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This creates `~/.gmail-streamer/profiles/my-profile/` with a template `config.yaml`.
|
|
66
|
+
|
|
67
|
+
### 3. Add your Gmail OAuth credentials
|
|
68
|
+
|
|
69
|
+
Place your `credentials.json` (from [Google Cloud Console](https://console.cloud.google.com/apis/credentials)) into the profile directory.
|
|
70
|
+
|
|
71
|
+
### 4. Configure the profile
|
|
72
|
+
|
|
73
|
+
Edit `~/.gmail-streamer/profiles/my-profile/config.yaml`:
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
filter: "from:example@gmail.com has:attachment"
|
|
77
|
+
target_directory: "./downloads"
|
|
78
|
+
mode: "full" # or "attachments_only"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 5. Run
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
gmail-streamer run my-profile
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
On first run, a browser window opens for OAuth authorization. Subsequent runs reuse the cached token.
|
|
88
|
+
|
|
89
|
+
## 📁 Profile Resolution
|
|
90
|
+
|
|
91
|
+
The profiles directory is resolved in this order:
|
|
92
|
+
|
|
93
|
+
1. `--profile-dir` flag or `GMAIL_STREAMER_PROFILE_DIR` env var
|
|
94
|
+
2. `./profiles/` in the current working directory (if it exists)
|
|
95
|
+
3. `~/.gmail-streamer/profiles/` (default)
|
|
96
|
+
|
|
97
|
+
The `profile` argument can be a **name** (looked up in the profiles directory) or a **path** to an existing directory (backward compatible).
|
|
98
|
+
|
|
99
|
+
## 🛠️ CLI Reference
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
gmail-streamer run <profile> # Download messages
|
|
103
|
+
gmail-streamer --profile-dir /path run <profile> # Custom profiles directory
|
|
104
|
+
gmail-streamer profiles list # List available profiles
|
|
105
|
+
gmail-streamer profiles init <name> # Scaffold a new profile
|
|
106
|
+
gmail-streamer profiles show <name> # Show profile config
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## ⚙️ Profile Structure
|
|
110
|
+
|
|
111
|
+
Each profile lives in its own directory with:
|
|
112
|
+
|
|
113
|
+
| File | Purpose |
|
|
114
|
+
|------|---------|
|
|
115
|
+
| `config.yaml` | Filter query, target directory, download mode |
|
|
116
|
+
| `credentials.json` | OAuth client credentials (you provide this) |
|
|
117
|
+
| `token.json` | Auto-generated after first OAuth flow |
|
|
118
|
+
|
|
119
|
+
## 🏗️ Architecture
|
|
120
|
+
|
|
121
|
+
| Module | Responsibility |
|
|
122
|
+
|--------|---------------|
|
|
123
|
+
| `cli.py` | Click CLI entry point (group with `run` and `profiles` subcommands) |
|
|
124
|
+
| `paths.py` | Profile directory resolution and discovery |
|
|
125
|
+
| `config.py` | Loads and validates `config.yaml` into a `ProfileConfig` dataclass |
|
|
126
|
+
| `auth.py` | OAuth2 flow with token caching |
|
|
127
|
+
| `gmail_client.py` | Gmail API wrapper: search, fetch messages, fetch attachments |
|
|
128
|
+
| `storage.py` | Saves `.eml` files and attachments to disk |
|
|
129
|
+
|
|
130
|
+
## 📄 License
|
|
131
|
+
|
|
132
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="logo.png" alt="gmail-streamer" width="512"/>
|
|
3
|
+
|
|
4
|
+
[](https://python.org)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**📧 Download Gmail messages matching your filters to local files 📥**
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- **🗂️ Profile-based configuration** — run multiple independent download profiles, each with its own filters, credentials, and output directory
|
|
14
|
+
- **🔐 OAuth2 authentication** — secure Google sign-in with automatic token caching
|
|
15
|
+
- **📧 Full message download** — save complete `.eml` files for archival
|
|
16
|
+
- **📎 Attachments-only mode** — grab just the attachments, skip the rest
|
|
17
|
+
- **🧠 Incremental downloads** — remembers what's already been downloaded, no duplicates across runs
|
|
18
|
+
- **🔍 Gmail search filters** — use any Gmail search query (`from:`, `has:attachment`, `after:`, label filters, etc.)
|
|
19
|
+
- **🏠 Works from anywhere** — install globally with `uv` and run from any directory
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/tsilva/gmail-streamer.git
|
|
27
|
+
cd gmail-streamer
|
|
28
|
+
uv tool install . --force --no-cache
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Create a profile
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gmail-streamer profiles init my-profile
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This creates `~/.gmail-streamer/profiles/my-profile/` with a template `config.yaml`.
|
|
38
|
+
|
|
39
|
+
### 3. Add your Gmail OAuth credentials
|
|
40
|
+
|
|
41
|
+
Place your `credentials.json` (from [Google Cloud Console](https://console.cloud.google.com/apis/credentials)) into the profile directory.
|
|
42
|
+
|
|
43
|
+
### 4. Configure the profile
|
|
44
|
+
|
|
45
|
+
Edit `~/.gmail-streamer/profiles/my-profile/config.yaml`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
filter: "from:example@gmail.com has:attachment"
|
|
49
|
+
target_directory: "./downloads"
|
|
50
|
+
mode: "full" # or "attachments_only"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 5. Run
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gmail-streamer run my-profile
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On first run, a browser window opens for OAuth authorization. Subsequent runs reuse the cached token.
|
|
60
|
+
|
|
61
|
+
## 📁 Profile Resolution
|
|
62
|
+
|
|
63
|
+
The profiles directory is resolved in this order:
|
|
64
|
+
|
|
65
|
+
1. `--profile-dir` flag or `GMAIL_STREAMER_PROFILE_DIR` env var
|
|
66
|
+
2. `./profiles/` in the current working directory (if it exists)
|
|
67
|
+
3. `~/.gmail-streamer/profiles/` (default)
|
|
68
|
+
|
|
69
|
+
The `profile` argument can be a **name** (looked up in the profiles directory) or a **path** to an existing directory (backward compatible).
|
|
70
|
+
|
|
71
|
+
## 🛠️ CLI Reference
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
gmail-streamer run <profile> # Download messages
|
|
75
|
+
gmail-streamer --profile-dir /path run <profile> # Custom profiles directory
|
|
76
|
+
gmail-streamer profiles list # List available profiles
|
|
77
|
+
gmail-streamer profiles init <name> # Scaffold a new profile
|
|
78
|
+
gmail-streamer profiles show <name> # Show profile config
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## ⚙️ Profile Structure
|
|
82
|
+
|
|
83
|
+
Each profile lives in its own directory with:
|
|
84
|
+
|
|
85
|
+
| File | Purpose |
|
|
86
|
+
|------|---------|
|
|
87
|
+
| `config.yaml` | Filter query, target directory, download mode |
|
|
88
|
+
| `credentials.json` | OAuth client credentials (you provide this) |
|
|
89
|
+
| `token.json` | Auto-generated after first OAuth flow |
|
|
90
|
+
|
|
91
|
+
## 🏗️ Architecture
|
|
92
|
+
|
|
93
|
+
| Module | Responsibility |
|
|
94
|
+
|--------|---------------|
|
|
95
|
+
| `cli.py` | Click CLI entry point (group with `run` and `profiles` subcommands) |
|
|
96
|
+
| `paths.py` | Profile directory resolution and discovery |
|
|
97
|
+
| `config.py` | Loads and validates `config.yaml` into a `ProfileConfig` dataclass |
|
|
98
|
+
| `auth.py` | OAuth2 flow with token caching |
|
|
99
|
+
| `gmail_client.py` | Gmail API wrapper: search, fetch messages, fetch attachments |
|
|
100
|
+
| `storage.py` | Saves `.eml` files and attachments to disk |
|
|
101
|
+
|
|
102
|
+
## 📄 License
|
|
103
|
+
|
|
104
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GIT_HASH = "4eb78ce"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Hatch build hook to embed git hash at build time."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CustomBuildHook(BuildHookInterface):
|
|
10
|
+
def initialize(self, version, build_data):
|
|
11
|
+
try:
|
|
12
|
+
git_hash = subprocess.check_output(
|
|
13
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
14
|
+
text=True,
|
|
15
|
+
stderr=subprocess.DEVNULL,
|
|
16
|
+
).strip()
|
|
17
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
18
|
+
git_hash = "unknown"
|
|
19
|
+
|
|
20
|
+
build_info_path = Path(self.root) / "src" / "gmail_streamer" / "_build_info.py"
|
|
21
|
+
build_info_path.write_text(f'GIT_HASH = "{git_hash}"\n')
|
|
22
|
+
|
|
23
|
+
# Force include the generated file
|
|
24
|
+
build_data["force_include"][str(build_info_path)] = "gmail_streamer/_build_info.py"
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gmail-streamer"
|
|
3
|
+
version = "0.2.2"
|
|
4
|
+
description = "CLI tool to download Gmail messages matching configurable filters via OAuth2"
|
|
5
|
+
authors = [{ name = "Tiago Silva" }]
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
keywords = ["gmail", "email", "download", "oauth2", "cli", "google", "attachments", "backup"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Intended Audience :: End Users/Desktop",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Topic :: Communications :: Email",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"google-api-python-client",
|
|
23
|
+
"google-auth-oauthlib",
|
|
24
|
+
"google-auth-httplib2",
|
|
25
|
+
"click",
|
|
26
|
+
"pyyaml",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
gmail-streamer = "gmail_streamer.cli:main"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/tsilva/gmail-streamer"
|
|
34
|
+
Repository = "https://github.com/tsilva/gmail-streamer"
|
|
35
|
+
Issues = "https://github.com/tsilva/gmail-streamer/issues"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["hatchling"]
|
|
39
|
+
build-backend = "hatchling.build"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/gmail_streamer"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.hooks.custom]
|
|
45
|
+
path = "hatch_build.py"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from google.auth.transport.requests import Request
|
|
4
|
+
from google.oauth2.credentials import Credentials
|
|
5
|
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
6
|
+
from googleapiclient.discovery import build
|
|
7
|
+
|
|
8
|
+
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_gmail_service(profile_dir: Path):
|
|
12
|
+
creds_path = profile_dir / "credentials.json"
|
|
13
|
+
token_path = profile_dir / "token.json"
|
|
14
|
+
|
|
15
|
+
creds = None
|
|
16
|
+
if token_path.exists():
|
|
17
|
+
creds = Credentials.from_authorized_user_file(str(token_path), SCOPES)
|
|
18
|
+
|
|
19
|
+
if not creds or not creds.valid:
|
|
20
|
+
if creds and creds.expired and creds.refresh_token:
|
|
21
|
+
creds.refresh(Request())
|
|
22
|
+
else:
|
|
23
|
+
if not creds_path.exists():
|
|
24
|
+
raise FileNotFoundError(
|
|
25
|
+
f"OAuth credentials not found: {creds_path}\n"
|
|
26
|
+
"Download from Google Cloud Console and place in profile directory."
|
|
27
|
+
)
|
|
28
|
+
flow = InstalledAppFlow.from_client_secrets_file(str(creds_path), SCOPES)
|
|
29
|
+
creds = flow.run_local_server(port=0)
|
|
30
|
+
with open(token_path, "w") as f:
|
|
31
|
+
f.write(creds.to_json())
|
|
32
|
+
|
|
33
|
+
return build("gmail", "v1", credentials=creds)
|