adding
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import re
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from apps.account.models import User
|
||||
from apps.banker.models import Contact, ContactGroup, MessageTemplate
|
||||
from apps.messaging.models import BulkMessage, Channel, MessageRecipient
|
||||
from apps.messaging.providers.bale import send_bale
|
||||
from apps.messaging.providers.eitaa import send_eitaa
|
||||
from apps.messaging.providers.sms import send_bulk_sms
|
||||
from apps.messaging.providers.telegram import send_telegram
|
||||
from apps.messaging.providers.whatsapp import send_whatsapp
|
||||
|
||||
_FIELD_PATTERN = re.compile(r"\{(.*?)\}")
|
||||
|
||||
_BOT_CHANNEL_SENDERS = {
|
||||
Channel.TELEGRAM: send_telegram,
|
||||
Channel.BALE: send_bale,
|
||||
Channel.EITAA: send_eitaa,
|
||||
}
|
||||
|
||||
|
||||
class TemplateRenderer:
|
||||
@staticmethod
|
||||
def field_values(contact: Contact) -> dict[str, str]:
|
||||
"""برچسب فیلد -> مقدار، برای جایگزینی {برچسب} در متن قالب.
|
||||
|
||||
فیلدهای پایه همیشه هستند؛ فیلدهای سفارشی از گروه(های)ی میآیند که
|
||||
این مخاطب عضوشان است (ContactGroup.member_ids شامل id این مخاطب).
|
||||
"""
|
||||
values = {"نام": contact.name, "شماره": contact.phone}
|
||||
# فیلتر JSON-level (member_ids__contains) روی SQLite اصلا قابلکامپایل
|
||||
# نیست (فقط PostgreSQL پشتیبانی میکند) — چون تعداد گروههای هر کاربر
|
||||
# کم است، اینجا فیلتر را سمت پایتون انجام میدهیم تا روی هر دو دیتابیس
|
||||
# کار کند.
|
||||
groups = [
|
||||
g
|
||||
for g in ContactGroup.objects.filter(user_id=contact.user_id)
|
||||
if contact.id in g.member_ids
|
||||
]
|
||||
for group in groups:
|
||||
for field in group.fields.all():
|
||||
value = contact.field_values.get(field.id)
|
||||
if value:
|
||||
values[field.label] = value
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def render(contact: Contact, template: MessageTemplate | None, body_override: str) -> str:
|
||||
body = template.body if template else body_override
|
||||
values = TemplateRenderer.field_values(contact)
|
||||
return _FIELD_PATTERN.sub(lambda m: values.get(m.group(1), m.group(0)), body)
|
||||
|
||||
|
||||
class BulkMessageService:
|
||||
@staticmethod
|
||||
def _resolve_contacts(user: User, group_id: str | None, contact_ids: list[str] | None) -> list[Contact]:
|
||||
if contact_ids:
|
||||
return list(Contact.objects.filter(user=user, id__in=contact_ids))
|
||||
if group_id:
|
||||
group = ContactGroup.objects.get(user=user, id=group_id)
|
||||
return list(Contact.objects.filter(user=user, id__in=group.member_ids))
|
||||
return list(Contact.objects.filter(user=user))
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create_and_dispatch(
|
||||
user: User,
|
||||
channels: list[str],
|
||||
group_id: str | None = None,
|
||||
contact_ids: list[str] | None = None,
|
||||
template_id: str | None = None,
|
||||
body_override: str = "",
|
||||
) -> BulkMessage:
|
||||
template = MessageTemplate.objects.filter(user=user, id=template_id).first() if template_id else None
|
||||
contacts = BulkMessageService._resolve_contacts(user, group_id, contact_ids)
|
||||
|
||||
bulk = BulkMessage.objects.create(
|
||||
user=user,
|
||||
group_id=group_id or None,
|
||||
template=template,
|
||||
body_override=body_override,
|
||||
channels=channels,
|
||||
)
|
||||
|
||||
rendered_by_contact = {c.id: TemplateRenderer.render(c, template, body_override) for c in contacts}
|
||||
|
||||
if Channel.SMS in channels:
|
||||
BulkMessageService._dispatch_sms(bulk, contacts, rendered_by_contact)
|
||||
for channel in (Channel.TELEGRAM, Channel.BALE, Channel.EITAA):
|
||||
if channel in channels:
|
||||
BulkMessageService._dispatch_bot_channel(bulk, contacts, rendered_by_contact, channel)
|
||||
if Channel.WHATSAPP in channels:
|
||||
BulkMessageService._dispatch_whatsapp(bulk, contacts, rendered_by_contact)
|
||||
|
||||
has_manual = bulk.recipients.filter(status=MessageRecipient.Status.NEEDS_MANUAL).exists()
|
||||
bulk.status = BulkMessage.Status.PARTIALLY_MANUAL if has_manual else BulkMessage.Status.DISPATCHED
|
||||
bulk.save(update_fields=["status"])
|
||||
return bulk
|
||||
|
||||
@staticmethod
|
||||
def _dispatch_sms(bulk: BulkMessage, contacts: list[Contact], rendered_by_contact: dict) -> None:
|
||||
pairs = [(c.phone, rendered_by_contact[c.id]) for c in contacts]
|
||||
results = send_bulk_sms(pairs)
|
||||
MessageRecipient.objects.bulk_create(
|
||||
MessageRecipient(
|
||||
bulk_message=bulk,
|
||||
contact=c,
|
||||
channel=Channel.SMS,
|
||||
status=MessageRecipient.Status.SENT if results.get(c.phone) else MessageRecipient.Status.FAILED,
|
||||
rendered_body=rendered_by_contact[c.id],
|
||||
)
|
||||
for c in contacts
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _dispatch_bot_channel(
|
||||
bulk: BulkMessage, contacts: list[Contact], rendered_by_contact: dict, channel: str
|
||||
) -> None:
|
||||
sender = _BOT_CHANNEL_SENDERS[channel]
|
||||
recipients = []
|
||||
for c in contacts:
|
||||
chat_id = c.channel_ids.get(channel)
|
||||
body = rendered_by_contact[c.id]
|
||||
if not chat_id:
|
||||
status = MessageRecipient.Status.NEEDS_MANUAL
|
||||
else:
|
||||
status = MessageRecipient.Status.SENT if sender(chat_id, body) else MessageRecipient.Status.FAILED
|
||||
recipients.append(
|
||||
MessageRecipient(
|
||||
bulk_message=bulk, contact=c, channel=channel, status=status, rendered_body=body
|
||||
)
|
||||
)
|
||||
MessageRecipient.objects.bulk_create(recipients)
|
||||
|
||||
@staticmethod
|
||||
def _dispatch_whatsapp(bulk: BulkMessage, contacts: list[Contact], rendered_by_contact: dict) -> None:
|
||||
from django.conf import settings
|
||||
|
||||
recipients = []
|
||||
configured = bool(settings.WHATSAPP_ACCESS_TOKEN and settings.WHATSAPP_PHONE_NUMBER_ID)
|
||||
for c in contacts:
|
||||
body = rendered_by_contact[c.id]
|
||||
if not configured:
|
||||
status = MessageRecipient.Status.NEEDS_MANUAL
|
||||
else:
|
||||
status = (
|
||||
MessageRecipient.Status.SENT
|
||||
if send_whatsapp(c.phone, body)
|
||||
else MessageRecipient.Status.FAILED
|
||||
)
|
||||
recipients.append(
|
||||
MessageRecipient(
|
||||
bulk_message=bulk, contact=c, channel=Channel.WHATSAPP, status=status, rendered_body=body
|
||||
)
|
||||
)
|
||||
MessageRecipient.objects.bulk_create(recipients)
|
||||
|
||||
@staticmethod
|
||||
def confirm_manual(user: User, bulk_id: int, recipient_id: int) -> MessageRecipient:
|
||||
recipient = MessageRecipient.objects.get(
|
||||
id=recipient_id, bulk_message_id=bulk_id, bulk_message__user=user
|
||||
)
|
||||
recipient.status = MessageRecipient.Status.MANUAL_CONFIRMED
|
||||
recipient.save(update_fields=["status"])
|
||||
return recipient
|
||||
Reference in New Issue
Block a user