feat: SMTP IMAP Proxy: add sendbox && UI: sendbox use split view (#248)

This commit is contained in:
Dream Hunter
2024-05-18 17:02:21 +08:00
committed by GitHub
parent 745e36f838
commit a5ff4f2d90
8 changed files with 353 additions and 288 deletions

View File

@@ -10,7 +10,7 @@ from twisted.internet import protocol, reactor, defer
from twisted.cred.checkers import ICredentialsChecker, IUsernamePassword
from config import settings
from parse_email import parse_email
from parse_email import generate_email_model, parse_email
from models import EmailModel
_logger = logging.getLogger(__name__)
@@ -58,7 +58,8 @@ class SimpleMessage:
@implementer(imap4.IMailboxInfo, imap4.IMailbox)
class SimpleMailbox:
def __init__(self, password):
def __init__(self, name, password):
self.name = name
self.password = password
self.listeners = []
self.addListener = self.listeners.append
@@ -104,6 +105,13 @@ class SimpleMailbox:
return defer.succeed(r)
def fetch(self, messages, uid):
if self.name == "INBOX":
return self.fetchINBOX(messages)
if self.name == "SENT":
return self.fetchSENT(messages)
return []
def fetchINBOX(self, messages):
start, end = messages.ranges[0]
start = max(start, 1)
if self.message_count > 0 and start > self.message_count:
@@ -127,6 +135,30 @@ class SimpleMailbox:
for uid, item in enumerate(reversed(res.json()["results"]))
]
def fetchSENT(self, messages):
start, end = messages.ranges[0]
start = max(start, 1)
if self.message_count > 0 and start > self.message_count:
return []
res = requests.get(
f"{settings.proxy_url}/api/sendbox?limit=20&offset={start - 1}", headers={
"Authorization": f"Bearer {self.password}",
"Content-Type": "application/json"
}
)
if res.status_code != 200:
_logger.error(
"Failed: "
f"code=[{res.status_code}] text=[{res.text}]"
)
raise Exception("Failed to fetch emails")
if res.json()["count"] > 0:
self.message_count = res.json()["count"]
return [
(start + uid, SimpleMessage(start + uid, generate_email_model(item)))
for uid, item in enumerate(reversed(res.json()["results"]))
]
def getUID(self, message):
return message.uid
@@ -141,11 +173,16 @@ class Account(imap4.MemoryAccount):
self.password = password
super().__init__(user)
def isSubscribed(self, name):
return name.upper() in ["INBOX", "SENT"]
def _emptyMailbox(self, name, id):
_logger.info(f"New mailbox: {name}, {id}")
if name != "INBOX":
raise imap4.NoSuchMailbox(name.encode("utf-8"))
return SimpleMailbox(self.password)
if name == "INBOX":
return SimpleMailbox(name, self.password)
if name == "SENT":
return SimpleMailbox(name, self.password)
raise imap4.NoSuchMailbox(name.encode("utf-8"))
def select(self, name, rw=1):
return imap4.MemoryAccount.select(self, name)
@@ -166,6 +203,7 @@ class SimpleRealm:
res = json.loads(avatarId)
account = Account(res["username"], res["password"])
account.addMailbox("INBOX")
account.addMailbox("SENT")
return imap4.IAccount, account, lambda: None

View File

@@ -1,6 +1,13 @@
import email
from email.message import Message
import datetime
import json
import logging
import email
from email.header import Header
from email.message import Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from models import EmailModel
@@ -36,3 +43,21 @@ def parse_email(raw: str) -> EmailModel:
size=len("could not parse email"),
subparts=[],
)
def generate_email_model(item: dict) -> EmailModel:
email_json = json.loads(item["raw"])
message = MIMEMultipart()
message['From'] = f"{email_json["from"]['name']} <{
email_json["from"]['email']}>"
message['To'] = ", ".join(
[f"{to['name']} <{to['email']}>" for to in email_json["personalizations"][0]["to"]])
message['Subject'] = email_json["subject"]
message["Date"] = datetime.datetime.strptime(
item["created_at"], "%Y-%m-%d %H:%M:%S"
).strftime("%a, %d %b %Y %H:%M:%S +0000")
message.attach(MIMEText(
email_json["content"][0]["value"],
"html" if "html" in email_json["content"][0]["type"] else "plain"
))
return parse_email(message.as_string())