Created
April 29, 2024 06:39
-
-
Save s3rgeym/248790fc607bcb04a6091c3a135d343d to your computer and use it in GitHub Desktop.
Dumps all emails via IMAP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import functools | |
import imaplib | |
import subprocess | |
import sys | |
import time | |
import zipfile | |
print_err = functools.partial(print, file=sys.stderr, flush=True) | |
def process_mailbox(M, mailbox, zip_file): | |
print_err(f"process {mailbox!r}") | |
rv, messages = M.search(None, "ALL") | |
if rv != "OK": | |
print_err("messages not found") | |
return | |
# print_err(messages) | |
message_nums = messages[0].decode().split() | |
print_err("total messages:", len(message_nums)) | |
for num in message_nums: | |
time.sleep(0.1) | |
rv, data = M.fetch(num, "(RFC822)") | |
if rv != "OK": | |
print_err("can't fetch", num) | |
continue | |
filename = f"./{mailbox}/{num}.eml" | |
print_err(f"add to archive: {filename!r}") | |
zip_file.writestr(filename, data[0][1]) | |
# Мне было лень разбираться как декодировать UTF-7 этот | |
def decode_mailbox(s): | |
try: | |
return subprocess.check_output( | |
"tr '&' + | iconv -f UTF-7 -t UTF-8", | |
shell=True, | |
input=s.encode(), | |
).decode() | |
except: # noqa: E722 | |
return s | |
def quote_if_needed(s): | |
return f'"{s}"' if any(c.isspace() for c in s) else s | |
def main(): | |
parser = argparse.ArgumentParser(description="Dumps all emails via IMAP") | |
parser.add_argument("-H", "--host", required=True) | |
parser.add_argument("-p", "--port", type=int, default=993) | |
parser.add_argument("-u", "--username", required=True) | |
parser.add_argument("-P", "--password", required=True) | |
parser.add_argument("-o", "--output", type=argparse.FileType("wb"), default="-") | |
args = parser.parse_args() | |
M = imaplib.IMAP4_SSL(args.host, args.port) | |
M.login(args.username, args.password) | |
# ('OK', [b'(\\HasNoChildren \\Inbox) "/" "INBOX"', b'(\\HasNoChildren \\Sent) "/" "Sent Messages"', b'(\\HasNoChildren \\Drafts) "/" "Drafts"', b'(\\HasNoChildren \\Trash) "/" "Deleted Messages"', b'(\\HasNoChildren \\Junk) "/" "Junk"', b'(\\HasNoChildren) "/" "&sLSsjMT0ulTHfNVo-"', b'(\\HasNoChildren) "/" "&zK2tbAC3rLDIHA-"', b'(\\HasNoChildren) "/" "&znTTmA-"', b'(\\HasNoChildren) "/" "SNS"', b'(\\HasNoChildren) "/" "&1QS4XLqowVg-"']) | |
rv, mailboxes = M.list() | |
assert rv == "OK" | |
mailboxes = [item.decode().split('"')[-2] for item in mailboxes] | |
print_err(mailboxes) | |
with zipfile.ZipFile( | |
args.output, "w", compression=zipfile.ZIP_DEFLATED | |
) as zip_file: | |
for mailbox in mailboxes: | |
# Бросит ошибку если имя содержит пробелы | |
# Вроде декодированное имя выбрать нельзя | |
rv, _ = M.select(quote_if_needed(mailbox)) | |
if mailbox.startswith("&"): | |
mailbox = decode_mailbox(mailbox) | |
if rv != "OK": | |
print_err(f"can't select mailbox: {mailbox}") | |
continue | |
try: | |
process_mailbox(M, mailbox, zip_file) | |
finally: | |
M.close() | |
M.logout() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment