-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgmail_client.py
More file actions
172 lines (143 loc) · 5.64 KB
/
gmail_client.py
File metadata and controls
172 lines (143 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
Gmail API Client Module
Handles authentication and email fetching from Gmail API
"""
import os
import base64
import pickle
from typing import List, Dict, Optional
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# Gmail API scope for read-only access
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
class GmailClient:
"""
Gmail API client for fetching and reading emails
Handles OAuth2 authentication and provides methods to interact with Gmail
"""
def __init__(self, credentials_file='Gmail_Credentials.json', token_file='token.pickle'):
"""
Initialize Gmail client with credentials
Args:
credentials_file: Path to Gmail API credentials JSON file
token_file: Path to store/load the access token
"""
self.credentials_file = credentials_file
self.token_file = token_file
self.service = None
self._authenticate()
def _authenticate(self):
"""
Authenticate with Gmail API using OAuth2
Creates service object for API calls
Handles token refresh and new authorization flow
"""
creds = None
# Load existing token if available
if os.path.exists(self.token_file):
with open(self.token_file, 'rb') as token:
creds = pickle.load(token)
# If no valid credentials, get new ones
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
# Refresh expired token
creds.refresh(Request())
else:
# Start new OAuth2 flow with fixed port
flow = InstalledAppFlow.from_client_secrets_file(
self.credentials_file, SCOPES)
creds = flow.run_local_server(port=8080)
# Save credentials for next run
with open(self.token_file, 'wb') as token:
pickle.dump(creds, token)
# Build Gmail API service
self.service = build('gmail', 'v1', credentials=creds)
def list_recent(self, max_results=30) -> List[Dict]:
"""
Fetch recent emails from Gmail inbox
Args:
max_results: Maximum number of emails to fetch (default: 30)
Returns:
List of dictionaries containing email metadata:
- id: Message ID
- threadId: Thread ID
- snippet: Email preview text
- sender: From address
- subject: Email subject
- date: Date received
"""
try:
# Fetch message list from inbox
results = self.service.users().messages().list(
userId='me',
maxResults=max_results,
labelIds=['INBOX']
).execute()
messages = results.get('messages', [])
if not messages:
return []
# Fetch metadata for each message
email_list = []
for msg in messages:
msg_data = self.service.users().messages().get(
userId='me',
id=msg['id'],
format='metadata',
metadataHeaders=['From', 'Subject', 'Date']
).execute()
# Extract headers
headers = {h['name']: h['value'] for h in msg_data.get('payload', {}).get('headers', [])}
email_list.append({
'id': msg_data['id'],
'threadId': msg_data['threadId'],
'snippet': msg_data.get('snippet', ''),
'sender': headers.get('From', 'Unknown'),
'subject': headers.get('Subject', '(No Subject)'),
'date': headers.get('Date', '')
})
return email_list
except HttpError as error:
print(f'Gmail API error: {error}')
return []
def get_raw(self, msg_id: str) -> Optional[bytes]:
"""
Fetch full raw email content by message ID
Args:
msg_id: Gmail message ID
Returns:
Raw email bytes in RFC 2822 format, or None if error
"""
try:
# Fetch raw message
message = self.service.users().messages().get(
userId='me',
id=msg_id,
format='raw'
).execute()
# Decode base64 encoded raw email
raw_bytes = base64.urlsafe_b64decode(message['raw'])
return raw_bytes
except HttpError as error:
print(f'Error fetching message {msg_id}: {error}')
return None
def get_full_message(self, msg_id: str) -> Optional[Dict]:
"""
Fetch complete email message with full payload
Args:
msg_id: Gmail message ID
Returns:
Full message dict with payload, headers, and parts
"""
try:
message = self.service.users().messages().get(
userId='me',
id=msg_id,
format='full'
).execute()
return message
except HttpError as error:
print(f'Error fetching full message {msg_id}: {error}')
return None