This commit is contained in:
j3d1 2024-05-03 23:34:47 +02:00
parent a4d79896da
commit 5ddd8c0b4b
15 changed files with 180 additions and 78 deletions

View file

@ -1,28 +0,0 @@
# Generated by Django 4.2.7 on 2024-04-26 13:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('mail', '0004_alter_emailattachment_file'),
]
operations = [
migrations.CreateModel(
name='UserNotificationChannel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('channel_type', models.CharField(choices=[('telegram', 'telegram'), ('email', 'email')], max_length=255)),
('channel_target', models.CharField(max_length=255)),
('event_filter', models.CharField(max_length=255)),
('active', models.BooleanField(default=True)),
('created', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -41,13 +41,4 @@ class EmailAttachment(AbstractFile):
name = models.CharField(max_length=255)
class UserNotificationChannel(models.Model):
user = models.ForeignKey(ExtendedUser, models.CASCADE)
channel_type = models.CharField(choices=[('telegram', 'telegram'), ('email', 'email')], max_length=255)
channel_target = models.CharField(max_length=255)
event_filter = models.CharField(max_length=255)
active = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
def validate_constraints(self, exclude=None): # TODO: email -> emailaddress, telegram -> chatid
return True

View file

@ -1,71 +0,0 @@
from aiohttp.client import ClientSession
from channels.layers import get_channel_layer
from channels.db import database_sync_to_async
from urllib.parse import quote as urlencode
from core.settings import TELEGRAM_BOT_TOKEN, TELEGRAM_GROUP_CHAT_ID
from mail.models import UserNotificationChannel
from mail.protocol import send_smtp, make_notification
async def http_get(url):
async with ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def telegram_notify(message, chat_id):
encoded_message = urlencode(message)
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage?chat_id={chat_id}&text={encoded_message}"
return await http_get(url)
async def email_notify(message, email):
mail = make_notification(message, email)
await send_smtp(mail)
class NotificationDispatcher:
channel_layer = None
room_group_name = "general"
def __init__(self):
self.channel_layer = get_channel_layer('default')
if not self.channel_layer:
raise Exception("Could not get channel layer")
@database_sync_to_async
def get_notification_targets(self):
channels = UserNotificationChannel.objects.filter(active=True)
return list(channels)
async def run_forever(self):
# Infinite loop to continuously listen for messages
print("Listening for messages...")
channel_name = await self.channel_layer.new_channel()
await self.channel_layer.group_add(self.room_group_name, channel_name)
print("Channel name:", channel_name)
while True:
# Blocking receive to get the message from the channel layer
message = await self.channel_layer.receive(channel_name)
if (message and 'type' in message and message['type'] == 'generic.event' and 'name' in message and
message['name'] == 'user_notification'):
if 'message' in message and 'event_id' in message:
await self.dispatch(message['message'], message['event_id'])
else:
print("Error: Invalid message format")
async def dispatch(self, message, event_id):
print("Dispatching message:", message, "with event_id:", event_id)
targets = await self.get_notification_targets()
await telegram_notify(message, TELEGRAM_GROUP_CHAT_ID)
for target in targets:
if target.channel_type == 'telegram':
print("Sending telegram notification to:", target.channel_target)
await telegram_notify(message, target.channel_target)
elif target.channel_type == 'email':
print("Sending email notification to:", target.channel_target)
await email_notify(message, target.channel_target)
else:
print("Unknown channel type:", target.channel_type)

View file

@ -5,8 +5,8 @@ from channels.layers import get_channel_layer
from channels.db import database_sync_to_async
from django.core.files.base import ContentFile
from core.settings import PRIMARY_HOST
from mail.models import Email, EventAddress, EmailAttachment
from notifications.templates import render_auto_reply
from notify_sessions.models import SystemEvent
from tickets.models import IssueThread
@ -83,14 +83,13 @@ def make_reply(reply_email, references=None, event=None):
return reply
def make_notification(message, to, event=None): # TODO where should replies to this go
def make_notification(message, to, title): # TODO where should replies to this go
from email.message import EmailMessage
from core.settings import MAIL_DOMAIN
event = event or "mail"
notification = EmailMessage()
notification["From"] = "notifications@%s" % MAIL_DOMAIN
notification["To"] = to
notification["Subject"] = f"System3 Notification"
notification["Subject"] = f"[C3LF Notification]%s" % title
# notification["Reply-To"] = f"{event}@{MAIL_DOMAIN}"
# notification["In-Reply-To"] = email.reference
# notification["Message-ID"] = email.id + "@" + MAIL_DOMAIN
@ -198,11 +197,11 @@ def receive_email(envelope, log=None):
header_in_reply_to = parsed.get('In-Reply-To')
header_message_id = parsed.get('Message-ID')
#if header_from != envelope.mail_from:
# if header_from != envelope.mail_from:
# log.warning("Header from does not match envelope from")
# log.info(f"Header from: {header_from}, envelope from: {envelope.mail_from}")
#
#if header_to != envelope.rcpt_tos[0]:
#
# if header_to != envelope.rcpt_tos[0]:
# log.warning("Header to does not match envelope to")
# log.info(f"Header to: {header_to}, envelope to: {envelope.rcpt_tos[0]}")
@ -231,16 +230,7 @@ def receive_email(envelope, log=None):
references = collect_references(active_issue_thread)
if not sender.startswith('noreply'):
subject = f"Re: {subject} [#{active_issue_thread.short_uuid()}]"
body = '''Your request (#{}) has been received and will be reviewed by our lost&found angels.
We are reviewing incoming requests during the event and teardown. Immediately after the event, expect a delay as the \
workload is high. We will not forget about your request and get back in touch once we have updated information on your \
request. Requests for devices, wallets, credit cards or similar items will be handled with priority.
If you happen to find your lost item or just want to add additional information, please reply to this email. Please \
do not create a new request.
Your c3lf (Cloakroom + Lost&Found) Team'''.format(active_issue_thread.short_uuid())
body = render_auto_reply(active_issue_thread)
reply_email = Email.objects.create(
sender=recipient, recipient=sender, body=body, subject=subject,
in_reply_to=header_message_id, event=target_event, issue_thread=active_issue_thread)
@ -251,20 +241,7 @@ Your c3lf (Cloakroom + Lost&Found) Team'''.format(active_issue_thread.short_uuid
active_issue_thread.state = 'pending_open'
active_issue_thread.save()
notification = None
if len(active_issue_thread.name) > 50:
notify_subject = active_issue_thread.name[:47] + "..."
else:
notify_subject = active_issue_thread.name
eventslug = target_event.slug if target_event else "37C3" # TODO 37C3 should not be hardcoded
if new:
notification = f"""New issue \"{active_issue_thread.name}\" [{active_issue_thread.short_uuid()}] created
https://{PRIMARY_HOST}/{eventslug}/ticket/{active_issue_thread.id}/"""
else:
notification = f"""Reply to issue \"{active_issue_thread.name}\" [{active_issue_thread.short_uuid()}]
https://{PRIMARY_HOST}/{eventslug}/ticket/{active_issue_thread.id}/"""
return email, new, reply, notification
return email, new, reply, active_issue_thread
class LMTPHandler:
@ -286,7 +263,7 @@ class LMTPHandler:
content = None
try:
content = envelope.content
email, new, reply, notification = await database_sync_to_async(receive_email)(envelope, log)
email, new, reply, thread = await database_sync_to_async(receive_email)(envelope, log)
log.info(f"Created email {email.id}")
systemevent = await database_sync_to_async(SystemEvent.objects.create)(type='email received',
reference=email.id)
@ -296,11 +273,10 @@ class LMTPHandler:
'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,
"message": "email received"})
log.info(f"Sent message to frontend")
if notification:
log.info(f"Sending notification {notification}")
if thread:
await channel_layer.group_send(
'general', {"type": "generic.event", "name": "user_notification", "event_id": systemevent.id,
"message": notification})
"ticket": thread, "new": new})
if new and reply:
log.info('Sending message to %s' % reply['To'])
await send_smtp(reply)

View file

@ -2,7 +2,7 @@ from django.contrib.auth.models import Permission
from django.test import TestCase
from authentication.models import ExtendedUser
from mail.models import UserNotificationChannel
from notifications.models import UserNotificationChannel
class UserNotificationTestCase(TestCase):