support also saving inline attachments
This commit is contained in:
parent
953351e8a8
commit
83dc4405bf
|
@ -30,7 +30,6 @@ Does not support different accounts.
|
||||||
```
|
```
|
||||||
4. Edit `/etc/secrets/imapviewer` with this content:
|
4. Edit `/etc/secrets/imapviewer` with this content:
|
||||||
```shell
|
```shell
|
||||||
SQLITE3_DB=<path to your sqlite3 database>
|
|
||||||
HTTP_AUTH_PASS=<your encoded password>
|
HTTP_AUTH_PASS=<your encoded password>
|
||||||
EMAIL_ARCHIVE_ROOT=<archive root path>
|
EMAIL_ARCHIVE_ROOT=<archive root path>
|
||||||
```
|
```
|
||||||
|
|
|
@ -26,11 +26,15 @@ def main(args):
|
||||||
logger.critical('Bad config file.')
|
logger.critical('Bad config file.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
save_inline_attachments = config.get('save_inline_attachments', False)
|
||||||
|
if save_inline_attachments:
|
||||||
|
logger.info('Saving inline attachments as well')
|
||||||
|
|
||||||
attachments_dir = Path(config['attachments_path'])
|
attachments_dir = Path(config['attachments_path'])
|
||||||
attachments_dir.mkdir(parents=True, exist_ok=True)
|
attachments_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
database = EmailDatabase(Path(config['database_path']))
|
database = EmailDatabase(Path(config['database_path']))
|
||||||
mail = MailConnection(config['server'], config['username'], config['password'], attachments_dir)
|
mail = MailConnection(config['server'], config['username'], config['password'], attachments_dir, save_inline_attachments=save_inline_attachments)
|
||||||
mail.load_folders()
|
mail.load_folders()
|
||||||
|
|
||||||
if config['server'] == 'imap.gmail.com':
|
if config['server'] == 'imap.gmail.com':
|
||||||
|
|
|
@ -5,6 +5,10 @@ password: password123
|
||||||
database_path: emails.db
|
database_path: emails.db
|
||||||
attachments_path: attachments
|
attachments_path: attachments
|
||||||
|
|
||||||
|
# Should inline content be saved as well?
|
||||||
|
This will result in images and other inline content being saved, not just attachments.
|
||||||
|
save_inline_attachments: false
|
||||||
|
|
||||||
exclude_folders:
|
exclude_folders:
|
||||||
- Trash
|
- Trash
|
||||||
- Drafts
|
- Drafts
|
||||||
|
|
|
@ -33,10 +33,11 @@ class FileAttachmentEncoder(JSONEncoder):
|
||||||
|
|
||||||
|
|
||||||
class MailConnection:
|
class MailConnection:
|
||||||
def __init__(self, host: str, username: str, password: str, attachments_dir: Path):
|
def __init__(self, host: str, username: str, password: str, attachments_dir: Path, save_inline_attachments: bool = False):
|
||||||
self.mail = imaplib.IMAP4_SSL(host)
|
self.mail = imaplib.IMAP4_SSL(host)
|
||||||
self.mail.login(username, password)
|
self.mail.login(username, password)
|
||||||
self.attachments_dir = attachments_dir.expanduser().absolute().resolve()
|
self.attachments_dir = attachments_dir.expanduser().absolute().resolve()
|
||||||
|
self.save_inline_attachments = save_inline_attachments
|
||||||
self.folder_structure = {}
|
self.folder_structure = {}
|
||||||
self.logger = logging.getLogger('iarchiver.mail_conn')
|
self.logger = logging.getLogger('iarchiver.mail_conn')
|
||||||
self.logger.setLevel(logging.INFO)
|
self.logger.setLevel(logging.INFO)
|
||||||
|
@ -102,7 +103,7 @@ class MailConnection:
|
||||||
if email_message.is_multipart():
|
if email_message.is_multipart():
|
||||||
for part in email_message.walk():
|
for part in email_message.walk():
|
||||||
content_disposition = str(part.get("Content-Disposition"))
|
content_disposition = str(part.get("Content-Disposition"))
|
||||||
if "attachment" in content_disposition:
|
if 'attachment' in content_disposition or (self.save_inline_attachments and 'inline' in content_disposition):
|
||||||
filename = part.get_filename()
|
filename = part.get_filename()
|
||||||
if filename:
|
if filename:
|
||||||
# The filename of the file is the hash of its content, which should de-duplicate files.
|
# The filename of the file is the hash of its content, which should de-duplicate files.
|
||||||
|
@ -119,7 +120,7 @@ class MailConnection:
|
||||||
attachments.append(file_obj)
|
attachments.append(file_obj)
|
||||||
raw_email_clean = email_message.as_string()
|
raw_email_clean = email_message.as_string()
|
||||||
return unix_timestamp, to_header, from_header, subject, raw_email_clean, attachments
|
return unix_timestamp, to_header, from_header, subject, raw_email_clean, attachments
|
||||||
except Exception as e:
|
except:
|
||||||
self.logger.critical(traceback.format_exc())
|
self.logger.critical(traceback.format_exc())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
25
server.py
25
server.py
|
@ -6,13 +6,11 @@ from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from flask import Flask, render_template, send_from_directory
|
from flask import Flask, render_template, send_from_directory, abort
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
if not os.environ.get('SQLITE3_DB'):
|
|
||||||
raise Exception('SQLITE3_DB not set.')
|
|
||||||
if not os.environ.get('HTTP_AUTH_PASS'):
|
if not os.environ.get('HTTP_AUTH_PASS'):
|
||||||
raise Exception('HTTP_AUTH_PASS not set.')
|
raise Exception('HTTP_AUTH_PASS not set.')
|
||||||
if not os.environ.get('EMAIL_ARCHIVE_ROOT'):
|
if not os.environ.get('EMAIL_ARCHIVE_ROOT'):
|
||||||
|
@ -50,6 +48,8 @@ def index():
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def account(email_account):
|
def account(email_account):
|
||||||
conn = get_db_connection(email_account)
|
conn = get_db_connection(email_account)
|
||||||
|
if not conn:
|
||||||
|
abort(404)
|
||||||
folders = conn.execute('SELECT name, table_name FROM folders_mapping').fetchall()
|
folders = conn.execute('SELECT name, table_name FROM folders_mapping').fetchall()
|
||||||
syncs = conn.execute('SELECT * FROM syncs ORDER BY timestamp DESC').fetchall()
|
syncs = conn.execute('SELECT * FROM syncs ORDER BY timestamp DESC').fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -63,11 +63,13 @@ def account(email_account):
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def folder(email_account, table_name):
|
def folder(email_account, table_name):
|
||||||
conn = get_db_connection(email_account)
|
conn = get_db_connection(email_account)
|
||||||
|
if not conn:
|
||||||
|
abort(404)
|
||||||
emails = conn.execute(f'SELECT * FROM {table_name} ORDER BY timestamp DESC').fetchall()
|
emails = conn.execute(f'SELECT * FROM {table_name} ORDER BY timestamp DESC').fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
emails = [dict_from_row(email) for email in emails]
|
emails = [dict_from_row(email) for email in emails]
|
||||||
for email in emails:
|
for item in emails:
|
||||||
email['timestamp'] = datetime.fromtimestamp(email['timestamp']).strftime('%Y-%m-%d %H:%M:%S')
|
item['timestamp'] = datetime.fromtimestamp(item['timestamp']).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
return render_template('folder.html', emails=emails, table_name=table_name, email_account=email_account)
|
return render_template('folder.html', emails=emails, table_name=table_name, email_account=email_account)
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +77,8 @@ def folder(email_account, table_name):
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def email(email_account, table_name, id):
|
def email(email_account, table_name, id):
|
||||||
conn = get_db_connection(email_account)
|
conn = get_db_connection(email_account)
|
||||||
|
if not conn:
|
||||||
|
abort(404)
|
||||||
email = dict_from_row(conn.execute(f'SELECT * FROM {table_name} WHERE id = ?', (id,)).fetchone())
|
email = dict_from_row(conn.execute(f'SELECT * FROM {table_name} WHERE id = ?', (id,)).fetchone())
|
||||||
conn.close()
|
conn.close()
|
||||||
email['timestamp'] = datetime.fromtimestamp(email['timestamp']).strftime('%Y-%m-%d %H:%M:%S')
|
email['timestamp'] = datetime.fromtimestamp(email['timestamp']).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
@ -87,15 +91,18 @@ def email(email_account, table_name, id):
|
||||||
def download_file(email_account, filename):
|
def download_file(email_account, filename):
|
||||||
p = Path(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'attachments', filename)
|
p = Path(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'attachments', filename)
|
||||||
if not p.exists():
|
if not p.exists():
|
||||||
return 404
|
abort(404)
|
||||||
mimetype = magic.from_file(str(p), mime=True)
|
mimetype = magic.from_file(str(p), mime=True)
|
||||||
return send_from_directory(Path(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'attachments'), filename, mimetype=mimetype)
|
return send_from_directory(Path(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'attachments'), filename, mimetype=mimetype)
|
||||||
|
|
||||||
|
|
||||||
def get_db_connection(email_account):
|
def get_db_connection(email_account):
|
||||||
conn = sqlite3.connect(os.path.join(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'emails.db'))
|
try:
|
||||||
conn.row_factory = sqlite3.Row
|
conn = sqlite3.connect(os.path.join(os.environ.get('EMAIL_ARCHIVE_ROOT'), email_account, 'emails.db'))
|
||||||
return conn
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in New Issue