2018-08-13 12:20:09 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
"""
|
|
|
|
Meh mail client conf generator for other things
|
|
|
|
"""
|
|
|
|
|
|
|
|
import configparser
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
# TODO Find config file from XDG
|
|
|
|
# TODO Signature file
|
|
|
|
# TODO Write ~/.mail/[mailbox]/color file if required by sth?
|
2018-10-14 16:59:49 +02:00
|
|
|
# TODO Write in .config or .cache /mel
|
2018-08-13 17:59:40 +02:00
|
|
|
# TODO Fix IMAPS with mbsync
|
2018-08-13 12:20:09 +02:00
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
configPath = os.path.join(os.path.expanduser('~'), '.config', 'mel', 'accounts.conf')
|
2018-08-13 12:20:09 +02:00
|
|
|
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read(configPath)
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
storageFull = os.path.realpath(os.path.expanduser(config["GENERAL"]["storage"]))
|
|
|
|
config["GENERAL"]["storage"] = storageFull
|
|
|
|
|
2018-08-13 12:20:09 +02:00
|
|
|
SERVER_DEFAULTS = {
|
|
|
|
"imap": {"port": 143, "starttls": True},
|
|
|
|
"smtp": {"port": 587, "starttls": True},
|
|
|
|
}
|
|
|
|
SERVER_ITEMS = {"host", "port", "user", "pass", "starttls"}
|
2018-10-14 16:59:49 +02:00
|
|
|
ACCOUNT_DEFAULTS = {
|
|
|
|
"color": "#FFFFFF",
|
|
|
|
"color16": "0",
|
|
|
|
# "colormutt": "white",
|
|
|
|
"inboxfolder": "INBOX",
|
|
|
|
"archivefolder": "Archive",
|
|
|
|
"draftsfolder": "Drafts",
|
|
|
|
"sentfolder": "Sent",
|
|
|
|
"spamfolder": "Spam",
|
|
|
|
"trashfolder": "Trash",
|
|
|
|
}
|
2018-08-13 12:20:09 +02:00
|
|
|
|
|
|
|
# Reading sections
|
|
|
|
accounts = dict()
|
2018-08-13 17:59:40 +02:00
|
|
|
mails = set()
|
2018-08-13 12:20:09 +02:00
|
|
|
|
|
|
|
for name in config.sections():
|
|
|
|
if not name.islower():
|
|
|
|
continue
|
|
|
|
section = config[name]
|
|
|
|
|
|
|
|
data = dict()
|
|
|
|
for server in SERVER_DEFAULTS.keys():
|
|
|
|
for item in SERVER_ITEMS:
|
|
|
|
key = server + item
|
|
|
|
try:
|
|
|
|
val = section.get(key) or section.get(item) or SERVER_DEFAULTS[server][item]
|
|
|
|
except KeyError:
|
|
|
|
raise KeyError("{}.{}".format(name, key))
|
|
|
|
|
|
|
|
if isinstance(val, str):
|
|
|
|
if val == "True":
|
|
|
|
val = True
|
|
|
|
elif val == "False":
|
|
|
|
val = False
|
|
|
|
elif val.isnumeric():
|
|
|
|
val = int(val)
|
|
|
|
data[key] = val
|
|
|
|
|
|
|
|
for key in section.keys():
|
|
|
|
if key in SERVER_ITEMS:
|
|
|
|
continue
|
|
|
|
data[key] = section[key]
|
|
|
|
|
2018-10-14 16:59:49 +02:00
|
|
|
for k, v in config['DEFAULT'].items():
|
|
|
|
if k not in data:
|
|
|
|
data[k] = v
|
|
|
|
|
|
|
|
for k, v in ACCOUNT_DEFAULTS.items():
|
|
|
|
if k not in data:
|
|
|
|
data[k] = v
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
mails.add(section["from"])
|
|
|
|
if "alternatives" in section:
|
|
|
|
for alt in section["alternatives"].split(";"):
|
|
|
|
mails.add(alt)
|
|
|
|
|
|
|
|
data["account"] = name
|
|
|
|
data["storage"] = os.path.join(config['GENERAL']['storage'], name)
|
2018-08-13 12:20:09 +02:00
|
|
|
data["storageInbox"] = os.path.join(data["storage"], "INBOX")
|
|
|
|
accounts[name] = data
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
general = dict()
|
|
|
|
section = config["GENERAL"]
|
|
|
|
for key in section.keys():
|
|
|
|
general[key] = section[key]
|
|
|
|
general["main"] = accounts[general["main"]]
|
|
|
|
|
|
|
|
|
2018-08-13 12:20:09 +02:00
|
|
|
# OfflineIMAP
|
|
|
|
|
|
|
|
OFFLINEIMAP_BEGIN = """[general]
|
|
|
|
# List of accounts to be synced, separated by a comma.
|
|
|
|
accounts = {}
|
|
|
|
maxsyncaccounts = {}
|
|
|
|
stocktimeout = 60
|
|
|
|
pythonfile = ~/.config/offlineimap.py
|
|
|
|
|
|
|
|
[mbnames]
|
|
|
|
enabled = yes
|
|
|
|
filename = ~/.mutt/mailboxes
|
|
|
|
header = "mailboxes "
|
|
|
|
peritem = "+%(accountname)s/%(foldername)s"
|
|
|
|
sep = " "
|
|
|
|
footer = "\\n"
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
OFFLINEIMAP_ACCOUNT = """[Account {account}]
|
|
|
|
localrepository = {account}-local
|
|
|
|
remoterepository = {account}-remote
|
2018-08-13 12:20:09 +02:00
|
|
|
autorefresh = 0.5
|
|
|
|
quick = 10
|
|
|
|
utf8foldernames = yes
|
|
|
|
postsynchook = ~/.mutt/postsync
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
[Repository {account}-local]
|
2018-08-13 12:20:09 +02:00
|
|
|
type = Maildir
|
|
|
|
localfolders = {storage}
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
[Repository {account}-remote]
|
2018-08-13 12:20:09 +02:00
|
|
|
type = IMAP
|
|
|
|
{secconf}
|
|
|
|
keepalive = 60
|
|
|
|
holdconnectionopen = yes
|
|
|
|
remotehost = {imaphost}
|
|
|
|
remoteport = {imapport}
|
|
|
|
remoteuser = {imapuser}
|
|
|
|
remotepass = {imappass}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
offlineIMAPstr = OFFLINEIMAP_BEGIN.format(','.join(accounts), len(accounts))
|
|
|
|
for name, account in accounts.items():
|
|
|
|
if account["imapstarttls"]:
|
|
|
|
secconf = "ssl = no"
|
|
|
|
else:
|
|
|
|
secconf = "sslcacertfile = /etc/ssl/certs/ca-certificates.crt"
|
|
|
|
offlineIMAPstr += OFFLINEIMAP_ACCOUNT.format(**account, secconf=secconf)
|
|
|
|
# TODO Write
|
|
|
|
|
|
|
|
# mbsync
|
2018-08-13 17:59:40 +02:00
|
|
|
MBSYNC_ACCOUNT = """IMAPAccount {account}
|
2018-08-13 12:20:09 +02:00
|
|
|
Host {imaphost}
|
2018-08-13 17:59:40 +02:00
|
|
|
Port {imapport}
|
2018-08-13 12:20:09 +02:00
|
|
|
User {imapuser}
|
2018-08-13 17:59:40 +02:00
|
|
|
Pass "{imappassEscaped}"
|
2018-08-13 12:20:09 +02:00
|
|
|
{secconf}
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
IMAPStore {account}-remote
|
|
|
|
Account {account}
|
2018-08-13 12:20:09 +02:00
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
MaildirStore {account}-local
|
2018-08-13 12:20:09 +02:00
|
|
|
Subfolders Verbatim
|
|
|
|
Path {storage}/
|
|
|
|
Inbox {storageInbox}/
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
Channel {account}
|
|
|
|
Master :{account}-remote:
|
|
|
|
Slave :{account}-local:
|
2018-08-13 12:20:09 +02:00
|
|
|
Patterns *
|
|
|
|
Create Both
|
|
|
|
SyncState *
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
mbsyncStr = ""
|
|
|
|
for name, account in accounts.items():
|
|
|
|
if account["imapstarttls"]:
|
|
|
|
secconf = "SSLType STARTTLS"
|
|
|
|
else:
|
|
|
|
secconf = "SSLType IMAPS"
|
2018-08-13 17:59:40 +02:00
|
|
|
if "certificate" in account:
|
|
|
|
secconf += "\nCertificateFile {certificate}".format(**account)
|
|
|
|
imappassEscaped = account["imappass"].replace("\\", "\\\\")
|
|
|
|
mbsyncStr += MBSYNC_ACCOUNT.format(**account, secconf=secconf, imappassEscaped=imappassEscaped)
|
2018-10-14 16:59:49 +02:00
|
|
|
mbsyncFilepath = os.path.join(os.path.expanduser('~'), '.mbsyncrc')
|
|
|
|
with open(mbsyncFilepath, 'w') as f:
|
2018-08-13 12:20:09 +02:00
|
|
|
f.write(mbsyncStr)
|
|
|
|
|
|
|
|
# msmtp
|
|
|
|
MSMTP_BEGIN = """defaults
|
|
|
|
protocol smtp
|
|
|
|
auth on
|
|
|
|
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-08-13 17:59:40 +02:00
|
|
|
MSMTP_ACCOUNT = """account {account}
|
2018-08-13 12:20:09 +02:00
|
|
|
from {from}
|
|
|
|
user {smtpuser}
|
|
|
|
password {smtppass}
|
|
|
|
host {smtphost}
|
|
|
|
port {smtpport}
|
|
|
|
tls on
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
msmtpStr = MSMTP_BEGIN
|
|
|
|
for name, account in accounts.items():
|
|
|
|
msmtpStr += MSMTP_ACCOUNT.format(**account)
|
2018-10-14 16:59:49 +02:00
|
|
|
mbsyncFilepath = os.path.join(os.path.expanduser('~'), '.msmtprc')
|
|
|
|
with open(mbsyncFilepath, 'w') as f:
|
2018-08-13 12:20:09 +02:00
|
|
|
f.write(msmtpStr)
|
2018-08-13 17:59:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
# notmuch
|
|
|
|
NOTMUCH_BEGIN = """[database]
|
|
|
|
path={storage}
|
|
|
|
|
|
|
|
[user]
|
|
|
|
name={main[name]}
|
|
|
|
primary_email={main[from]}
|
|
|
|
other_email={other_email}
|
|
|
|
|
|
|
|
[new]
|
2018-08-14 17:23:57 +02:00
|
|
|
tags=unprocessed;unread;
|
2018-08-13 17:59:40 +02:00
|
|
|
ignore=
|
|
|
|
|
|
|
|
[search]
|
|
|
|
exclude_tags=deleted;spam;
|
|
|
|
|
|
|
|
[maildir]
|
|
|
|
synchronize_flags=true
|
|
|
|
|
|
|
|
[crypto]
|
|
|
|
gpg_path=gpg
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
other_email = mails.copy()
|
|
|
|
other_email.remove(general["main"]["from"])
|
|
|
|
other_email = ";".join(other_email)
|
|
|
|
notmuchStr = NOTMUCH_BEGIN.format(**general, other_email=other_email)
|
2018-10-14 16:59:49 +02:00
|
|
|
mbsyncFilepath = os.path.join(os.path.expanduser('~'), '.notmuchrc')
|
|
|
|
with open(mbsyncFilepath, 'w') as f:
|
2018-08-13 17:59:40 +02:00
|
|
|
f.write(notmuchStr)
|
|
|
|
|
2018-10-14 16:59:49 +02:00
|
|
|
# mutt (temp)
|
|
|
|
|
|
|
|
## mailboxes
|
|
|
|
MAILBOXES_BEGIN = "mailboxes"
|
|
|
|
|
|
|
|
mailboxesStr = MAILBOXES_BEGIN
|
|
|
|
for name, account in accounts.items():
|
|
|
|
lines = "-" * (20 - len(name))
|
|
|
|
mailboxesStr += f' "+{name}{lines}"'
|
|
|
|
for root, dirs, files in os.walk(account['storage']):
|
|
|
|
if "cur" not in dirs or "new" not in dirs or "tmp" not in dirs:
|
|
|
|
continue
|
|
|
|
assert root.startswith(storageFull)
|
|
|
|
path = root[len(storageFull)+1:]
|
|
|
|
mailboxesStr += f' "+{path}"'
|
|
|
|
mailboxesStr += "\n"
|
|
|
|
mailboxesFilepath = os.path.join(os.path.expanduser('~'), '.mutt/mailboxes')
|
|
|
|
with open(mailboxesFilepath, 'w') as f:
|
|
|
|
f.write(mailboxesStr)
|
|
|
|
|
|
|
|
## accounts
|
|
|
|
# TODO html mails
|
|
|
|
|
|
|
|
MUTT_ACCOUNT = """set from = "{from}"
|
|
|
|
set sendmail = "/usr/bin/msmtp -a {account}"
|
|
|
|
set realname = "{name}"
|
|
|
|
set spoolfile = "+{account}/{inboxfolder}"
|
|
|
|
set mbox = "+{account}/{archivefolder}"
|
|
|
|
set postponed = "+{account}/{draftsfolder}"
|
|
|
|
set record = "+{account}/{sentfolder}"
|
|
|
|
set trash = "+{account}/{trashfolder}"
|
|
|
|
set signature = "~/.mutt/accounts/{account}.sig"
|
|
|
|
set content_type = "text/plain"
|
|
|
|
set sig_dashes = yes
|
|
|
|
|
|
|
|
color status {colormutt} default
|
|
|
|
|
|
|
|
macro index D \\
|
|
|
|
"<clear-flag>N<save-message>+{account}/{trashfolder}<enter>" \\
|
|
|
|
"move message to the trash"
|
|
|
|
|
|
|
|
macro index S \\
|
|
|
|
"<clear-flag>N<save-message>+{account}/{spamfolder}<enter>" \\
|
|
|
|
"mark message as spam"
|
|
|
|
# vim: syntax=muttrc
|
|
|
|
"""
|
|
|
|
|
|
|
|
for name, account in accounts.items():
|
|
|
|
muttStr = MUTT_ACCOUNT.format(**account)
|
|
|
|
|
|
|
|
# Config
|
|
|
|
muttFilepath = os.path.join(os.path.expanduser('~'), f'.mutt/accounts/{name}')
|
|
|
|
with open(muttFilepath, 'w') as f:
|
|
|
|
f.write(muttStr)
|
|
|
|
|
|
|
|
# Signature
|
|
|
|
sigStr = account.get("sig", account.get("name", ""))
|
|
|
|
sigFilepath = os.path.join(os.path.expanduser('~'), f'.mutt/accounts/{name}.sig')
|
|
|
|
with open(sigFilepath, 'w') as f:
|
|
|
|
f.write(sigStr)
|
|
|
|
|
|
|
|
MUTT_SELECTOR = """
|
|
|
|
set folder = "{storage}"
|
|
|
|
source ~/.mutt/mailboxes
|
|
|
|
|
|
|
|
source ~/.mutt/accounts/{main[account]}
|
|
|
|
|
|
|
|
{hooks}
|
|
|
|
|
|
|
|
source ~/.mutt/custom
|
|
|
|
|
|
|
|
# vim: syntax=muttrc
|
|
|
|
"""
|
|
|
|
|
|
|
|
selectStr = ""
|
|
|
|
hooks = ""
|
|
|
|
for name, account in accounts.items():
|
|
|
|
hooks += f"folder-hook {name}/* source ~/.mutt/accounts/{name}\n"
|
|
|
|
selectStr += MUTT_SELECTOR.format(**general, hooks=hooks)
|
|
|
|
selectFilepath = os.path.join(os.path.expanduser('~'), '.mutt/muttrc')
|
|
|
|
with open(selectFilepath, 'w') as f:
|
|
|
|
f.write(selectStr)
|
|
|
|
|