stash
This commit is contained in:
parent
e361556188
commit
432b61c70a
16 changed files with 181 additions and 78 deletions
0
core/notifications/__init__.py
Normal file
0
core/notifications/__init__.py
Normal file
16
core/notifications/defauls.py
Normal file
16
core/notifications/defauls.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
auto_reply_body = '''Your request (#{{ ticket_uuid }}) 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'''
|
||||
|
||||
new_issue_notification = '''New issue "{{ ticket_name | limit_length }}" [{{ ticket_uuid }}] created
|
||||
{{ ticket_url }}'''
|
||||
|
||||
reply_issue_notification = '''Reply to issue "{{ ticket_name }}" [{{ ticket_uuid }}]
|
||||
{{ ticket_url }}'''
|
73
core/notifications/dispatch.py
Normal file
73
core/notifications/dispatch.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
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.protocol import send_smtp, make_notification
|
||||
from notifications.models import UserNotificationChannel
|
||||
from notifications.templates import render_notification_new_ticket, render_notification_reply_ticket
|
||||
|
||||
|
||||
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 'ticket' in message and 'event_id' in message and 'new' in message:
|
||||
await self.dispatch(message['ticket'], message['event_id'], message['new'])
|
||||
else:
|
||||
print("Error: Invalid message format")
|
||||
|
||||
async def dispatch(self, ticket, event_id, new):
|
||||
message = render_notification_new_ticket(ticket) if new else render_notification_reply_ticket(ticket)
|
||||
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)
|
48
core/notifications/migrations/0001_initial.py
Normal file
48
core/notifications/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Generated by Django 4.2.7 on 2024-05-03 21:02
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
from notifications.defauls import auto_reply_body, new_issue_notification, reply_issue_notification
|
||||
from notifications.models import MessageTemplate
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
def create_required_templates(apps, schema_editor):
|
||||
MessageTemplate.objects.create(name='auto_reply', message=auto_reply_body)
|
||||
MessageTemplate.objects.create(name='new_issue_notification', message=new_issue_notification)
|
||||
MessageTemplate.objects.create(name='reply_issue_notification', message=reply_issue_notification)
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MessageTemplate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('message', models.TextField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('marked_confidential', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
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)),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(create_required_templates),
|
||||
]
|
0
core/notifications/migrations/__init__.py
Normal file
0
core/notifications/migrations/__init__.py
Normal file
22
core/notifications/models.py
Normal file
22
core/notifications/models.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.db import models
|
||||
|
||||
from authentication.models import ExtendedUser
|
||||
|
||||
|
||||
class MessageTemplate(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
message = models.TextField()
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
marked_confidential = models.BooleanField(default=False)
|
||||
|
||||
|
||||
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
|
54
core/notifications/templates.py
Normal file
54
core/notifications/templates.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import jinja2
|
||||
from core.settings import PRIMARY_HOST
|
||||
|
||||
from notifications.models import MessageTemplate
|
||||
|
||||
# auto_reply_title = f"Re: {{ ticket_name }} [#{{ ticket_uuid }}]"
|
||||
|
||||
|
||||
TEMLATE_VARS = ['ticket_name', 'ticket_uuid', 'ticket_id', 'ticket_url',
|
||||
'event_slug', 'event_name',
|
||||
'username', 'user_nick',
|
||||
'web_host'] # TODO customer_name, tracking_code
|
||||
|
||||
|
||||
def limit_length(s, length=50):
|
||||
if len(s) > length:
|
||||
return s[:(length - 3)] + "..."
|
||||
return s
|
||||
|
||||
|
||||
def ticket_url(ticket):
|
||||
eventslug = ticket.event.slug if ticket.event else "37C3" # TODO 37C3 should not be hardcoded
|
||||
return f"https://{PRIMARY_HOST}/{eventslug}/ticket/{ticket.id}/"
|
||||
|
||||
|
||||
def render_template(template, **kwargs):
|
||||
try:
|
||||
|
||||
environment = jinja2.Environment()
|
||||
environment.filters['limit_length'] = limit_length
|
||||
|
||||
tmpl = MessageTemplate.objects.get(name=template)
|
||||
template = environment.from_string(tmpl.message)
|
||||
return template.render(**kwargs, web_host=PRIMARY_HOST)
|
||||
except MessageTemplate.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def render_auto_reply(ticket):
|
||||
eventslug = ticket.event.slug if ticket.event else "37C3" # TODO 37C3 should not be hardcoded
|
||||
return render_template('auto_reply', ticket_name=ticket.name, ticket_uuid=ticket.short_uuid(),
|
||||
ticket_id=ticket.id, event_slug=eventslug, ticket_url=ticket_url(ticket))
|
||||
|
||||
|
||||
def render_notification_new_ticket(ticket):
|
||||
eventslug = ticket.event.slug if ticket.event else "37C3" # TODO 37C3 should not be hardcoded
|
||||
return render_template('new_issue_notification', ticket_name=ticket.name, ticket_uuid=ticket.short_uuid(),
|
||||
ticket_id=ticket.id, event_slug=eventslug, ticket_url=ticket_url(ticket))
|
||||
|
||||
|
||||
def render_notification_reply_ticket(ticket):
|
||||
eventslug = ticket.event.slug if ticket.event else "37C3" # TODO 37C3 should not be hardcoded
|
||||
return render_template('reply_issue_notification', ticket_name=ticket.name, ticket_uuid=ticket.short_uuid(),
|
||||
ticket_id=ticket.id, event_slug=eventslug, ticket_url=ticket_url(ticket))
|
0
core/notifications/tests/__init__.py
Normal file
0
core/notifications/tests/__init__.py
Normal file
Loading…
Add table
Add a link
Reference in a new issue