diff --git a/.gitignore b/.gitignore index 6e96963..fa22f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,6 @@ cython_debug/ # E.g. copies of old dev database *.bkp + +# Database dumps +/*.json diff --git a/shiftregister/feedback/management/__init__.py b/shiftregister/feedback/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shiftregister/feedback/management/commands/__init__.py b/shiftregister/feedback/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shiftregister/feedback/management/commands/notify.py b/shiftregister/feedback/management/commands/notify.py new file mode 100644 index 0000000..66fc381 --- /dev/null +++ b/shiftregister/feedback/management/commands/notify.py @@ -0,0 +1,135 @@ +import json +import pathlib +import sys + +from django.core.management.base import BaseCommand, CommandError +from django.template import Context, Template +from phonenumber_field.phonenumber import PhoneNumber + +from shiftregister.app.models import Helper +from shiftregister.messaging import Message, MessageType +from shiftregister.messaging.outbound import send + + +class Command(BaseCommand): + help = ( + "Notify a list of phone numbers excluding those already found in the database." + ) + + def add_arguments(self, parser): + parser.add_argument( + "-n", + "--dry-run", + action="store_true", + help="print messages that would be sent, but do not actually send them", + ) + parser.add_argument( + "numbers", help="path to numbers list as JSON", type=pathlib.Path + ) + + def handle(self, *args, **options): + with options["numbers"].open() as f: + numbers = json.load(f) + + if not isinstance(numbers, list) or not all( + map(lambda o: isinstance(o, dict) and "phone" in o, numbers) + ): + raise CommandError( + "JSON document must be an array of objects containing (at least) the key 'phone'" + ) + + template = Template(sys.stdin.read().strip()) + + numbers_count = len(numbers) + existing_numbers = set(Helper.objects.values_list("phone", flat=True)) + numbers = list( + filter( + lambda o: PhoneNumber.from_string(o["phone"]) not in existing_numbers, + numbers, + ) + ) + + self.stderr.write( + self.style.WARNING( + f"{numbers_count-len(numbers)} already found in database" + ) + ) + + messages = ( + Message( + i, + recipient=o["phone"], + text=template.render(Context(o)), + type=MessageType.OUTBOUND, + ) + for i, o in enumerate(numbers) + ) + + if options["dry_run"]: + messages = list(messages) + + self.stderr.write( + self.style.WARNING(f"would send {len(messages)} message(s)") + ) + self.stderr.write() + + for message in messages: + if int(message.key) != 0: + self.stderr.write() + self.stderr.write(self.style.WARNING("---")) + self.stderr.write() + + self.stderr.write(self.style.WARNING(f"to: {message.recipient}")) + self.stderr.write(self.style.WARNING(f"length: {len(message.text)}")) + self.stderr.write() + self.stderr.write(self.style.WARNING(message.text)) + + if len(messages) > 0: + self.stderr.write() + + return + + all_messages = set(messages) + sent_messages = set() + + self.stderr.write( + self.style.SUCCESS( + f"{'0'.rjust(len(str(len(all_messages))))} / {len(all_messages)}" + ), + ending="", + ) + + error = None + try: + for message in send(all_messages): + sent_messages.add(message) + + self.stderr.write( + self.style.SUCCESS( + f"\r{str(len(sent_messages)).rjust(len(str(len(all_messages))))} / {len(all_messages)}" + ), + ending="", + ) + except Exception as e: + error = e + + self.stderr.write() + self.stderr.write( + self.style.WARNING(f"sent {len(sent_messages)} out of {len(all_messages)}") + ) + self.stderr.write( + self.style.WARNING(f"numbers to which messages could not be sent:") + ) + self.stdout.write( + self.style.ERROR( + json.dumps( + [ + {"phone": message.recipient} + for message in all_messages - sent_messages + ] + ) + ) + ) + + if error: + raise CommandError(error) diff --git a/shiftregister/messaging/message.py b/shiftregister/messaging/message.py index e24fb02..98a534b 100644 --- a/shiftregister/messaging/message.py +++ b/shiftregister/messaging/message.py @@ -45,11 +45,10 @@ class Message: self.created_at = created_at def __eq__(self, other): - if other is None: - return False - elif isinstance(other, Message): - other = other.key - else: - other = str(other) + if isinstance(other, Message): + return self.key == other.key - return self.key == other + return NotImplemented + + def __hash__(self): + return hash(self.key)