Skip to content

Commit 32b4e96

Browse files
committed
skip message fetch if pre-persisted; add get-labels utility script
Signed-off-by: AbhishekKr <abhikumar163@gmail.com>
1 parent 0d286a3 commit 32b4e96

File tree

7 files changed

+201
-14
lines changed

7 files changed

+201
-14
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
> by default it reads mails first and stores it locally in a sqlite DB separated by year of mail, then deletes
1818
>
1919
> How to use: `python3 delete-mails.py ./config-yamls/delete-mails-config.yaml`
20+
>
21+
> GMail API doc: [developers.google.com/gmail/api/v1/reference/users/messages/delete](https://developers.google.com/gmail/api/v1/reference/users/messages/delete)
2022
2123
---
2224

_config_/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ def config_yaml():
1111
Args: None
1212
"""
1313
try:
14-
return sys.argv[1]
14+
if os.path.exists(sys.argv[1]):
15+
return sys.argv[1]
16+
else:
17+
return "config.yaml"
1518
except:
1619
try:
1720
return os.environ("GMAIL_HELPER_CONFIG")
@@ -84,3 +87,10 @@ def message_ids_to_skip():
8487
return ret_val
8588
else:
8689
return ret_val.split(",")
90+
91+
def labels_to_skip():
92+
ret_val = env_else_yaml("labels_to_skip")
93+
if isinstance(ret_val, list):
94+
return ret_val
95+
else:
96+
return ret_val.split(",")

_dbms_/__init__.py

+97-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import sqlite3
33
from sqlite3 import Error
4+
import sys
45

56
import _logging_ as _log
67
import _config_ as _cfg
@@ -29,7 +30,7 @@ def create_schema_messages(db):
2930
Args:
3031
db: Local db connection object.
3132
"""
32-
_log.logger.debug("creating table message")
33+
_log.logger.debug("creating table messages")
3334
cursorObj = db.cursor()
3435
cursorObj.execute("""CREATE TABLE IF NOT EXISTS messages(
3536
id text,
@@ -50,6 +51,23 @@ def create_schema_messages(db):
5051
db.commit()
5152

5253

54+
def create_schema_labels(db):
55+
"""Creates 'labels' schema on given DB connection.
56+
57+
Args:
58+
db: Local db connection object.
59+
"""
60+
_log.logger.debug("creating table labels")
61+
cursorObj = db.cursor()
62+
cursorObj.execute("""CREATE TABLE IF NOT EXISTS labels(
63+
name text,
64+
label blob,
65+
CONSTRAINT name_pk PRIMARY KEY (name))""")
66+
cursorObj.execute("""CREATE INDEX IF NOT EXISTS idx_labels_name
67+
ON labels (name);""")
68+
db.commit()
69+
70+
5371
def sender_of_message(message):
5472
try:
5573
return [item['value'] for item in message['payload']['headers'] if item['name'] == 'From'][0]
@@ -78,13 +96,26 @@ def date_of_message(message):
7896
return ""
7997

8098

99+
def message_exists(db, message_id):
100+
sql_stmt = """SELECT * FROM messages WHERE id = '%s';""" % (message_id)
101+
cursorObj = db.cursor()
102+
result = cursorObj.execute(sql_stmt)
103+
del cursorObj
104+
if result.fetchone() == None:
105+
return False
106+
return True
107+
108+
81109
def add_message(db, message):
82-
"""Get and Delete a list of messages from GMail by Query.
110+
"""Persist a message from GMail if doesn't already exists locally.
83111
84112
Args:
85113
db: Local db connection object.
86114
message: GMail message JSON object as read by get api.
87115
"""
116+
if message_exists(db, message['id']):
117+
_log.logger.info("message:%s already exists, skipping db entry" % (message['id']))
118+
return
88119
_log.logger.debug("[+] adding message: %s" % (message['id']))
89120

90121
cursorObj = db.cursor()
@@ -113,12 +144,60 @@ def add_message(db, message):
113144
subject_of_message(message)))
114145
_log.logger.debug("---------------------")
115146
db.commit()
147+
del cursorObj
148+
return True
116149
except:
117150
_log.logger.error("failed to insert for %s" % (message['id']))
118-
sys.exit(0)
151+
return False
152+
153+
154+
def label_exists(db, label_name):
155+
sql_stmt = """SELECT * FROM labels WHERE name = '%s';""" % (label_name)
156+
cursorObj = db.cursor()
157+
result = cursorObj.execute(sql_stmt)
158+
del cursorObj
159+
if result.fetchone() == None:
160+
return False
161+
return True
162+
163+
164+
def add_label(db, label):
165+
"""Persist a label from GMail if doesn't already exists locally.
166+
167+
Args:
168+
db: Local db connection object.
169+
label: GMail label
170+
"""
171+
label_name = label['name']
172+
173+
if label_exists(db, label_name):
174+
_log.logger.info("label:%s already exists, skipping db entry" % (label_name))
175+
return
119176

177+
_log.logger.debug("[+] adding label: %s" % (label_name))
120178

121-
def dbpath_by_year(year):
179+
cursorObj = db.cursor()
180+
sql_stmt = """INSERT INTO labels
181+
(name, label)
182+
VALUES (?,?)"""
183+
values = (
184+
label_name,
185+
str(label),
186+
)
187+
188+
try:
189+
cursorObj.execute(sql_stmt, values)
190+
_log.logger.debug("adding label: %s" % (label_name))
191+
_log.logger.debug("---------------------")
192+
db.commit()
193+
del cursorObj
194+
return True
195+
except:
196+
_log.logger.error("failed to insert for %s" % (label_name))
197+
return False
198+
199+
200+
def dbpath_by_token(basename, token):
122201
"""Returns DB path generated based on year.
123202
124203
Args:
@@ -127,7 +206,7 @@ def dbpath_by_year(year):
127206
Returns:
128207
DB filesystem path.
129208
"""
130-
db_name = "gmail-to-delete-%d.db" % (year)
209+
db_name = "%s-%s.db" % (str(basename), str(token))
131210
return os.path.join(_cfg.data_basepath(), db_name)
132211

133212

@@ -140,4 +219,16 @@ def connection_by_year(year):
140219
Returns:
141220
Local db connection object.
142221
"""
143-
return sql_connection(dbpath_by_year(year))
222+
return sql_connection(dbpath_by_token("gmail-to-delete", year))
223+
224+
225+
def connection_by_feature(feature):
226+
"""Returns SQLite3 connection to db created at path for given year.
227+
228+
Args:
229+
feature: Name of feature/construct; like mails, labels, spams, etc
230+
231+
Returns:
232+
Local db connection object.
233+
"""
234+
return sql_connection(dbpath_by_token("gmail", feature))

_google_/auth/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,16 @@ def gmail_messages():
4848
creds = google_oauth_creds()
4949
service = build('gmail', 'v1', credentials=creds, cache_discovery=False)
5050
return service.users().messages()
51+
52+
53+
def gmail_labels():
54+
"""Returns GMail labels object exposing labels API.
55+
56+
Args: None
57+
58+
Returns:
59+
GMail labels object.
60+
"""
61+
creds = google_oauth_creds()
62+
service = build('gmail', 'v1', credentials=creds, cache_discovery=False)
63+
return service.users().labels()

_google_/gmail/__init__.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
import _logging_ as _log
66

77

8+
def get_labels(db, labels_obj, user_id='me'):
9+
results = labels_obj.list(userId='me').execute()
10+
labels = results.get('labels', [])
11+
12+
if not labels:
13+
_log.logger.info("No labels found.")
14+
return
15+
16+
print('Labels:')
17+
for label in labels:
18+
if _db.add_label(db, label):
19+
print("~ added: %s" % (label['name']))
20+
21+
822
def get_mail(db, messages_obj, msg_id, user_id='me'):
923
"""Fetch a message from GMail by id and adds it to passed db.
1024
@@ -18,8 +32,13 @@ def get_mail(db, messages_obj, msg_id, user_id='me'):
1832
message = messages_obj.get(
1933
userId=user_id, id=msg_id, format='full'
2034
).execute()
35+
36+
if set.intersection(set(_cfg.labels_to_skip()), set(message['labelIds'])) != set():
37+
return False
2138
_log.logger.debug("adding message: %s" % (message['id']))
22-
_db.add_message(db, message)
39+
if not _db.add_message(db, message):
40+
return False
41+
return True
2342

2443

2544
def delete_mail(messages_obj, msg_id, user_id='me'):
@@ -34,8 +53,8 @@ def delete_mail(messages_obj, msg_id, user_id='me'):
3453
try:
3554
messages_obj.delete(userId=user_id, id=msg_id).execute()
3655
_log.logger.info('Message with id: %s deleted successfully.' % msg_id)
37-
except:
38-
_log.logger.error('An error occurred: %s' % error)
56+
except Exception as e:
57+
_log.logger.error('An error occurred: %s' % str(e))
3958

4059

4160
def mails_by_query(messages_obj, user_id='me', query=''):
@@ -67,8 +86,8 @@ def mails_by_query(messages_obj, user_id='me', query=''):
6786
messages.extend(response['messages'])
6887

6988
return messages
70-
except:
71-
_log.logger.error('An error occurred: %s' % error)
89+
except Exception as e:
90+
_log.logger.error('An error occurred: %s' % str(e))
7291
return []
7392

7493

@@ -87,5 +106,7 @@ def get_delete_mails_by_query(db, messages_obj, user_id='me', query=''):
87106
for message in mails_by_query(messages_obj, user_id, query):
88107
if message['id'] in message_ids_to_skip: continue
89108
_log.logger.debug(message['id'])
90-
get_mail(db, messages_obj, message['id'])
91-
delete_mail(messages_obj, message['id'])
109+
if get_mail(db, messages_obj, message['id']):
110+
delete_mail(messages_obj, message['id'])
111+
else:
112+
_log.logger.error("not deleting mail: %s" % (message['id']))

config-yamls/delete-mails-config.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ filters_to_delete:
2020
- "from:(googlegroups.com)"
2121

2222
message_ids_to_skip: []
23+
24+
labels_to_skip: []

get-labels.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2018 abhishekkr <abhikumar163@gmail.com>
2+
"""
3+
How to use:
4+
python3 get-labels.py ./config-yamls/delete-mails-config.yaml
5+
6+
7+
How to configure:
8+
9+
* using "get-labels-config.yaml", update all values to required configuration
10+
11+
> * 'gmail_credential_jsonpath' is file path for OAuth2 credential json file downloaded from Google by following step from README
12+
> * 'gmail_auth_picklepath' need to be file paths where Google credentials can be stored for reuse
13+
> * 'scopes' is a list of GMail API scopes made available to Google Auth
14+
> * 'data_basepath' is under which all DBs would be created
15+
>
16+
> would be better if all above mentioned file paths should be at a secure location r/w only by your user
17+
18+
19+
* using env variables
20+
21+
> all above mentioned config names prefixed with 'GMAIL_HELPER_' formulate their env var name
22+
"""
23+
24+
from __future__ import print_function
25+
import base64
26+
import email
27+
import sys
28+
29+
import _logging_ as _log
30+
import _config_ as _cfg
31+
import _google_.auth as _gauth
32+
import _google_.gmail as _gmail
33+
import _dbms_ as _db
34+
35+
36+
def main():
37+
"""Helps make a local backup of all lables."""
38+
labels_obj = _gauth.gmail_labels()
39+
40+
db = _db.connection_by_feature("labels")
41+
_db.create_schema_labels(db)
42+
_gmail.get_labels(db, labels_obj, 'me')
43+
db.close()
44+
del db
45+
46+
47+
if __name__ == '__main__':
48+
main()

0 commit comments

Comments
 (0)