add /ws/2/notify/ socket
This commit is contained in:
parent
97503e91e0
commit
101fa7b69d
10 changed files with 138 additions and 81 deletions
|
@ -1,50 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from helper import create_task
|
|
||||||
|
|
||||||
|
|
||||||
def make_reply(message, to, subject):
|
|
||||||
from email.message import EmailMessage
|
|
||||||
from core.settings import MAIL_DOMAIN
|
|
||||||
|
|
||||||
reply = EmailMessage()
|
|
||||||
reply["From"] = "noreply@" + MAIL_DOMAIN
|
|
||||||
reply["To"] = to
|
|
||||||
reply["Subject"] = subject
|
|
||||||
reply.set_content(message)
|
|
||||||
|
|
||||||
return reply
|
|
||||||
|
|
||||||
|
|
||||||
async def send_smtp(message):
|
|
||||||
await asyncio.sleep(30)
|
|
||||||
import aiosmtplib
|
|
||||||
log = logging.getLogger('mail.log')
|
|
||||||
log.info('Sending message to %s' % message['To'])
|
|
||||||
|
|
||||||
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
|
||||||
|
|
||||||
|
|
||||||
class LMTPHandler:
|
|
||||||
async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
|
|
||||||
from core.settings import MAIL_DOMAIN
|
|
||||||
if not address.endswith('@' + MAIL_DOMAIN):
|
|
||||||
return '550 not relaying to that domain'
|
|
||||||
envelope.rcpt_tos.append(address)
|
|
||||||
return '250 OK'
|
|
||||||
|
|
||||||
async def handle_DATA(self, server, session, envelope):
|
|
||||||
log = logging.getLogger('mail.log')
|
|
||||||
log.info('Message from %s' % envelope.mail_from)
|
|
||||||
log.info('Message for %s' % envelope.rcpt_tos)
|
|
||||||
log.info('Message data:\n')
|
|
||||||
for ln in envelope.content.decode('utf8', errors='replace').splitlines():
|
|
||||||
log.info(f'> {ln}'.strip())
|
|
||||||
log.info('End of message')
|
|
||||||
|
|
||||||
create_task(send_smtp(make_reply("Thank you for your message.", envelope.mail_from, 'Message received')))
|
|
||||||
|
|
||||||
# asyncio.create_task(send_reply())
|
|
||||||
|
|
||||||
return '250 Message accepted for delivery'
|
|
0
core/mail/migrations/__init__.py
Normal file
0
core/mail/migrations/__init__.py
Normal file
69
core/mail/protocol.py
Normal file
69
core/mail/protocol.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import logging
|
||||||
|
import aiosmtplib
|
||||||
|
|
||||||
|
|
||||||
|
def make_reply(message, to, subject):
|
||||||
|
from email.message import EmailMessage
|
||||||
|
from core.settings import MAIL_DOMAIN
|
||||||
|
|
||||||
|
reply = EmailMessage()
|
||||||
|
reply["From"] = "noreply@" + MAIL_DOMAIN
|
||||||
|
reply["To"] = to
|
||||||
|
reply["Subject"] = subject
|
||||||
|
reply.set_content(message)
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
async def send_smtp(message, log):
|
||||||
|
log.info('Sending message to %s' % message['To'])
|
||||||
|
await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False)
|
||||||
|
|
||||||
|
|
||||||
|
class LMTPHandler:
|
||||||
|
async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
|
||||||
|
from core.settings import MAIL_DOMAIN
|
||||||
|
if not address.endswith('@' + MAIL_DOMAIN):
|
||||||
|
return '550 not relaying to that domain'
|
||||||
|
envelope.rcpt_tos.append(address)
|
||||||
|
return '250 OK'
|
||||||
|
|
||||||
|
async def handle_DATA(self, server, session, envelope):
|
||||||
|
import email
|
||||||
|
log = logging.getLogger('mail.log')
|
||||||
|
log.info('Message from %s' % envelope.mail_from)
|
||||||
|
log.info('Message for %s' % envelope.rcpt_tos)
|
||||||
|
log.info('Message data:\n')
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = email.message_from_bytes(envelope.content)
|
||||||
|
body = ""
|
||||||
|
if parsed.is_multipart():
|
||||||
|
for part in parsed.walk():
|
||||||
|
ctype = part.get_content_type()
|
||||||
|
cdispo = str(part.get('Content-Disposition'))
|
||||||
|
|
||||||
|
# skip any text/plain (txt) attachments
|
||||||
|
if ctype == 'text/plain' and 'attachment' not in cdispo:
|
||||||
|
body = part.get_payload(decode=True)
|
||||||
|
else:
|
||||||
|
log.info("Attachment", ctype, cdispo)
|
||||||
|
else:
|
||||||
|
body = parsed.get_payload(decode=True)
|
||||||
|
log.info(body)
|
||||||
|
|
||||||
|
header_from = parsed.get('From')
|
||||||
|
header_to = parsed.get('To')
|
||||||
|
|
||||||
|
if header_from != envelope.mail_from:
|
||||||
|
log.warning("Header from does not match envelope from")
|
||||||
|
|
||||||
|
if header_to != envelope.rcpt_tos[0]:
|
||||||
|
log.warning("Header to does not match envelope to")
|
||||||
|
|
||||||
|
await send_smtp(make_reply("Thank you for your message.", envelope.mail_from, 'Message received'), log)
|
||||||
|
log.info("Sent reply")
|
||||||
|
return '250 Message accepted for delivery'
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e)
|
||||||
|
return '550 Message rejected'
|
0
core/mail/tests/__init__.py
Normal file
0
core/mail/tests/__init__.py
Normal file
0
core/mail/tests/v2/__init__.py
Normal file
0
core/mail/tests/v2/__init__.py
Normal file
|
@ -10,8 +10,9 @@ import uvicorn
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
from helper import init_loop
|
from helper import init_loop
|
||||||
from lmtp.protocol import LMTPHandler
|
from mail.protocol import LMTPHandler
|
||||||
from lmtp.socket import UnixSocketLMTPController
|
from mail.socket import UnixSocketLMTPController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# async def handle_echo(reader, writer):
|
# async def handle_echo(reader, writer):
|
||||||
|
|
|
@ -24,7 +24,9 @@ export default {
|
||||||
components: {Toast, Navbar, AddItemModal},
|
components: {Toast, Navbar, AddItemModal},
|
||||||
computed: mapState(['loadedItems', 'layout', 'toasts']),
|
computed: mapState(['loadedItems', 'layout', 'toasts']),
|
||||||
data: () => ({
|
data: () => ({
|
||||||
addModalOpen: false
|
addModalOpen: false,
|
||||||
|
notify_socket: null,
|
||||||
|
socket_toast: null,
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['removeToast', 'createToast']),
|
...mapMutations(['removeToast', 'createToast']),
|
||||||
|
@ -33,35 +35,68 @@ export default {
|
||||||
},
|
},
|
||||||
closeAddModal() {
|
closeAddModal() {
|
||||||
this.addModalOpen = false;
|
this.addModalOpen = false;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created: function () {
|
tryConnect() {
|
||||||
this.notify_socket = new WebSocket('wss://' + window.location.host + '/ws/notify/');
|
if (!this.notify_socket || this.notify_socket.readyState !== WebSocket.OPEN) {
|
||||||
this.notify_socket.onmessage = (e) => {
|
if (this.socket_toast) {
|
||||||
let data = JSON.parse(e.data);
|
this.removeToast(this.socket_toast.key);
|
||||||
console.log(data, e.data);
|
this.socket_toast = null;
|
||||||
};
|
}
|
||||||
|
this.socket_toast = this.createToast({
|
||||||
|
title: "Connecting...",
|
||||||
|
message: "Connecting to websocket...",
|
||||||
|
color: "warning"
|
||||||
|
});
|
||||||
|
this.notify_socket = new WebSocket('wss://' + window.location.host + '/ws/2/notify/');
|
||||||
this.notify_socket.onopen = (e) => {
|
this.notify_socket.onopen = (e) => {
|
||||||
this.createToast({
|
if (this.socket_toast) {
|
||||||
|
this.removeToast(this.socket_toast.key);
|
||||||
|
this.socket_toast = null;
|
||||||
|
}
|
||||||
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection established",
|
title: "Connection established",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
color: "success"
|
color: "success"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
this.notify_socket.onclose = (e) => {
|
this.notify_socket.onclose = (e) => {
|
||||||
this.createToast({
|
if (this.socket_toast) {
|
||||||
|
this.removeToast(this.socket_toast.key);
|
||||||
|
this.socket_toast = null;
|
||||||
|
}
|
||||||
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection closed",
|
title: "Connection closed",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
color: "danger"
|
color: "danger"
|
||||||
});
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.tryConnect();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
this.notify_socket.onerror = (e) => {
|
this.notify_socket.onerror = (e) => {
|
||||||
this.createToast({
|
if (this.socket_toast) {
|
||||||
|
this.removeToast(this.socket_toast.key);
|
||||||
|
this.socket_toast = null;
|
||||||
|
}
|
||||||
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection error",
|
title: "Connection error",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
color: "danger"
|
color: "danger"
|
||||||
});
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.tryConnect();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
this.notify_socket.onmessage = (e) => {
|
||||||
|
let data = JSON.parse(e.data);
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.tryConnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -84,8 +84,10 @@ const store = new Vuex.Store({
|
||||||
state.loadedItems.push(item);
|
state.loadedItems.push(item);
|
||||||
},
|
},
|
||||||
createToast(state, {title, message, color}) {
|
createToast(state, {title, message, color}) {
|
||||||
state.toasts.push({title, message, color, key: state.keyIncrement});
|
var toast = {title, message, color, key: state.keyIncrement}
|
||||||
|
state.toasts.push(toast);
|
||||||
state.keyIncrement += 1;
|
state.keyIncrement += 1;
|
||||||
|
return toast;
|
||||||
},
|
},
|
||||||
removeToast(state, key) {
|
removeToast(state, key) {
|
||||||
state.toasts = state.toasts.filter(toast => toast.key !== key);
|
state.toasts = state.toasts.filter(toast => toast.key !== key);
|
||||||
|
|
Loading…
Reference in a new issue