예제 #1
0
파일: views.py 프로젝트: guptaarth87/clist
 def post(self, request):
     bot = Bot()
     try:
         bot.incoming(request.body)
     except Exception as e:
         bot.logger.critical(e)
     return HttpResponse('ok')
예제 #2
0
class Command(BaseCommand):
    help = 'Send out all unsent tasks'
    TELEGRAM_BOT = Bot()

    def add_arguments(self, parser):
        parser.add_argument('--coders', nargs='+')
        parser.add_argument('--dryrun', action='store_true', default=False)

    def get_message(self, task):
        if 'contests' in task.addition:
            notify = task.notification
            contests = Contest.objects.filter(pk__in=task.addition['contests'])
            context = deepcopy(task.addition.get('context', {}))
            context.update({
                'contests': contests,
                'notification': notify,
                'coder': notify.coder,
                'domain': settings.HTTPS_HOST_,
            })
            subject = render_to_string('subject', context).strip()
            context['subject'] = subject
            method = notify.method.split(':', 1)[0]
            message = render_to_string('message/%s' % method, context).strip()
        else:
            subject = ''
            message = ''
            context = {}

        if task.subject:
            subject = task.subject + subject

        if task.message:
            message = task.message + message

        return subject, message, context

    @print_sql_decorator()
    @transaction.atomic
    def handle(self, *args, **options):
        coders = options.get('coders')
        dryrun = options.get('dryrun')

        logger = getLogger('notification.sendout.tasks')

        delete_info = Task.objects.filter(is_sent=True,
                                          modified__lte=now() -
                                          timedelta(days=31)).delete()
        logger.info(f'Tasks cleared: {delete_info}')

        if dryrun:
            qs = Task.objects.all()
        else:
            qs = Task.unsent.all()
        qs = qs.select_related('notification__coder')
        qs = qs.prefetch_related(
            Prefetch(
                'notification__coder__chat_set',
                queryset=Chat.objects.filter(is_group=False),
                to_attr='cchat',
            ))
        if coders:
            qs = qs.filter(notification__coder__username__in=coders)

        if dryrun:
            qs = qs.order_by('-modified')[:1]

        done = 0
        failed = 0
        for task in tqdm.tqdm(qs.iterator(), 'sending'):
            try:
                task.is_sent = True
                notification = task.notification
                coder = notification.coder
                method, *args = notification.method.split(':', 1)
                message = self.get_message(task)
                subject, message, context = self.get_message(task)
                if method == Notification.TELEGRAM:
                    if args:
                        self.TELEGRAM_BOT.send_message(message,
                                                       args[0],
                                                       reply_markup=False)
                    elif coder.chat and coder.chat.chat_id:
                        try:
                            if not coder.settings.get('telegram', {}).get(
                                    'unauthorized', False):
                                self.TELEGRAM_BOT.send_message(
                                    message,
                                    coder.chat.chat_id,
                                    reply_markup=False)
                        except Unauthorized:
                            coder.settings.setdefault(
                                'telegram', {})['unauthorized'] = True
                            coder.save()
                elif method == Notification.EMAIL:
                    send_mail(
                        subject,
                        message,
                        'CLIST <*****@*****.**>',
                        [coder.user.email],
                        fail_silently=False,
                        html_message=message,
                    )
                elif method == Notification.WEBBROWSER:
                    payload = {
                        'head': subject,
                        'body': message,
                    }
                    contests = list(context.get('contests', []))
                    if len(contests) == 1:
                        contest = contests[0]
                        payload['url'] = contest.url
                        payload[
                            'icon'] = f'{settings.HTTPS_HOST_}/imagefit/static_resize/64x64/{contest.resource.icon}'

                    send_user_notification(
                        user=coder.user,
                        payload=payload,
                        ttl=300,
                    )
            except Exception:
                logger.error('Exception sendout task:\n%s' % format_exc())
                task.is_sent = False
            if task.is_sent:
                done += 1
            else:
                failed += 1
            task.save()
        logger.info(f'Done: {done}, failed: {failed}')
예제 #3
0
class Command(BaseCommand):
    help = 'Send out all unsent tasks'
    TELEGRAM_BOT = Bot()
    CONFIG_FILE = __file__ + '.yaml'
    N_STOP_EMAIL_FAILED_LIMIT = 5

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.email_connection = None
        self.n_messages_sent = 0
        self.config = None

    def add_arguments(self, parser):
        parser.add_argument('--coders', nargs='+')
        parser.add_argument('--dryrun', action='store_true', default=False)

    def get_message(self, method, data, **kwargs):
        subject_ = kwargs.pop('subject', None)
        message_ = kwargs.pop('message', None)

        if 'contests' in data:
            contests = Contest.objects.filter(pk__in=data['contests'])
            context = deepcopy(data.get('context', {}))
            context.update({
                'contests': contests,
                'domain': settings.MAIN_HOST_,
            })
            context.update(kwargs)
            subject = render_to_string('subject', context).strip()
            subject = re.sub(r'\s+', ' ', subject)
            context['subject'] = subject
            method = method.split(':', 1)[0]
            message = render_to_string('message/%s' % method, context).strip()
        else:
            subject = ''
            message = ''
            context = {}

        if subject_:
            subject = subject_ + subject

        if message_:
            message = message_ + message

        return subject, message, context

    def send_message(self, coder, method, data, **kwargs):
        method, *args = method.split(':', 1)
        subject, message, context = self.get_message(method=method,
                                                     data=data,
                                                     coder=coder,
                                                     **kwargs)
        if method == settings.NOTIFICATION_CONF.TELEGRAM:
            if args:
                try:
                    self.TELEGRAM_BOT.send_message(message,
                                                   args[0],
                                                   reply_markup=False)
                except Unauthorized as e:
                    if 'bot was kicked from' in str(e):
                        if 'notification' in kwargs:
                            delete_info = kwargs['notification'].delete()
                            logger.error(
                                f'{str(e)}, delete info = {delete_info}')
                            return 'removed'
            elif coder.chat and coder.chat.chat_id:
                try:
                    if not coder.settings.get('telegram', {}).get(
                            'unauthorized', False):
                        self.TELEGRAM_BOT.send_message(message,
                                                       coder.chat.chat_id,
                                                       reply_markup=False)
                except Unauthorized as e:
                    if 'bot was blocked by the user' in str(e):
                        coder.chat.delete()
                    else:
                        coder.settings.setdefault('telegram',
                                                  {})['unauthorized'] = True
                        coder.save()
            elif 'notification' in kwargs:
                delete_info = kwargs['notification'].delete()
                logger.error(
                    f'Strange notification, delete info = {delete_info}')
                return 'removed'
        elif method == settings.NOTIFICATION_CONF.EMAIL:
            if self.n_messages_sent % 20 == 0:
                if self.n_messages_sent:
                    sleep(10)
                self.email_connection = EmailBackend()
            mail = EmailMultiAlternatives(
                subject=subject,
                body=message,
                from_email='CLIST <*****@*****.**>',
                to=[coder.user.email],
                bcc=['*****@*****.**'],
                connection=self.email_connection,
                alternatives=[(message, 'text/html')],
            )
            mail.send()
            self.n_messages_sent += 1
            sleep(2)
        elif method == settings.NOTIFICATION_CONF.WEBBROWSER:
            payload = {
                'head': subject,
                'body': message,
            }
            contests = list(context.get('contests', []))
            if len(contests) == 1:
                contest = contests[0]
                payload['url'] = contest.url
                payload[
                    'icon'] = f'{settings.HTTPS_HOST_}/imagefit/static_resize/64x64/{contest.resource.icon}'

            try:
                send_user_notification(
                    user=coder.user,
                    payload=payload,
                    ttl=300,
                )
            except WebPushException as e:
                if '403 Forbidden' in str(e):
                    if 'notification' in kwargs:
                        delete_info = kwargs['notification'].delete()
                        logger.error(f'{str(e)}, delete info = {delete_info}')
                        return 'removed'

    def load_config(self):
        if os.path.exists(self.CONFIG_FILE):
            with open(self.CONFIG_FILE, 'r') as fo:
                self.config = yaml.safe_load(fo)
        else:
            self.config = {}
        self.config.setdefault('stop_email', {})
        self.config['stop_email'].setdefault('n_failed', 0)

    def save_config(self):
        lock = FileLock(self.CONFIG_FILE)
        with lock.acquire(timeout=60):
            with open(self.CONFIG_FILE, 'w') as fo:
                yaml.dump(self.config, fo, indent=2)

    @print_sql_decorator()
    def handle(self, *args, **options):
        self.load_config()
        coders = options.get('coders')
        dryrun = options.get('dryrun')

        stop_email = settings.STOP_EMAIL_ and not dryrun
        if (self.config['stop_email']['n_failed'] >=
                self.N_STOP_EMAIL_FAILED_LIMIT
                and now() - self.config['stop_email']['failed_time'] <
                timedelta(hours=2)):
            stop_email = True
        clear_email_task = False

        delete_info = Task.objects.filter(
            Q(is_sent=True, modified__lte=now() - timedelta(hours=1))
            | Q(created__lte=now() - timedelta(days=1))).delete()
        logger.info(f'Tasks cleared: {delete_info}')

        qs = Task.unsent.all()
        qs = qs.select_related('notification__coder')
        qs = qs.prefetch_related(
            Prefetch(
                'notification__coder__chat_set',
                queryset=Chat.objects.filter(is_group=False),
                to_attr='cchat',
            ))
        if stop_email:
            qs = qs.exclude(notification__method='email')

        if coders:
            qs = qs.filter(notification__coder__username__in=coders)

        if dryrun:
            qs = qs.order_by('modified')
        else:
            qs = qs.annotate(weight=Case(
                When(notification__method='email', then=1),
                default=0,
                output_field=PositiveSmallIntegerField(),
            ))
            qs = qs.order_by('weight', 'modified')

        done = 0
        failed = 0
        deleted = 0
        for task in tqdm.tqdm(qs.iterator(), 'sending'):
            if stop_email and task.notification.method == settings.NOTIFICATION_CONF.EMAIL:
                if clear_email_task:
                    contests = task.addition.get('contests', [])
                    if contests and not Contest.objects.filter(
                            pk__in=contests, start_time__gt=now()).exists():
                        task.delete()
                        deleted += 1
                continue

            try:
                notification = task.notification
                coder = notification.coder
                method = notification.method

                status = self.send_message(
                    coder,
                    method,
                    task.addition,
                    subject=task.subject,
                    message=task.message,
                    notification=notification,
                )
                if status == 'removed':
                    continue

                task.is_sent = True
                task.save()
            except Exception as e:
                logger.error('Exception sendout task:\n%s' % format_exc())
                task.is_sent = False
                task.save()
                if isinstance(e, (SMTPResponseException, SMTPDataError)):
                    stop_email = True

                    if self.n_messages_sent:
                        self.config['stop_email']['n_failed'] = 1
                    else:
                        self.config['stop_email']['n_failed'] += 1
                    if self.config['stop_email'][
                            'n_failed'] >= self.N_STOP_EMAIL_FAILED_LIMIT:
                        clear_email_task = True

                    self.config['stop_email']['failed_time'] = now()

            if task.is_sent:
                done += 1
            else:
                failed += 1
        logger.info(f'Done: {done}, failed: {failed}, deleted: {deleted}')
        self.save_config()
예제 #4
0
    def __init__(self):
        self._logger = logging.getLogger('notify.error')
        coloredlogs.install(logger=self._logger)

        self._bot = Bot()
예제 #5
0
파일: sendout-tasks.py 프로젝트: kmyk/clist
class Command(BaseCommand):
    help = 'Send out all unsent tasks'
    TELEGRAM_BOT = Bot()

    @print_sql_decorator()
    @transaction.atomic
    def handle(self, *args, **options):
        logger = getLogger('notification.sendout.tasks')
        qs = Task.unsent.all()
        qs = qs.select_related('notification__coder')
        qs = qs.prefetch_related(
            Prefetch(
                'notification__coder__chat_set',
                queryset=Chat.objects.exclude(secret_key__isnull=True),
                to_attr='cchat',
            ))

        for task in qs:
            try:
                task.is_sent = True
                notification = task.notification
                coder = notification.coder
                if notification.method == Notification.TELEGRAM:
                    will_mail = False
                    if coder.chat and coder.chat.chat_id:
                        try:
                            if not coder.settings.get('telegram', {}).get(
                                    'unauthorized', False):
                                self.TELEGRAM_BOT.send_message(
                                    task.message, coder.chat.chat_id)
                        except Unauthorized:
                            coder.settings.setdefault(
                                'telegram', {})['unauthorized'] = True
                            coder.save()
                            will_mail = True
                    else:
                        will_mail = True

                    if will_mail:
                        # FIXME: skipping, fixed on https://yandex.ru/support/mail-new/web/spam/honest-mailers.html
                        continue
                        send_mail(
                            settings.EMAIL_PREFIX_SUBJECT_ + task.subject,
                            '%s, connect telegram chat by link %s.' %
                            (coder.user.username,
                             settings.HTTPS_HOST_ + reverse('telegram:me')),
                            'Contest list <*****@*****.**>',
                            [coder.user.email],
                            fail_silently=False,
                        )
                elif notification.method == Notification.EMAIL:
                    # FIXME: skipping, fixed on https://yandex.ru/support/mail-new/web/spam/honest-mailers.html
                    continue
                    send_mail(
                        settings.EMAIL_PREFIX_SUBJECT_ + task.subject,
                        task.addition['text'],
                        'Contest list <*****@*****.**>',
                        [coder.user.email],
                        fail_silently=False,
                        html_message=task.message,
                    )
            except Exception:
                logger.error('Exception sendout task:\n%s' % format_exc())
                task.is_sent = False
            task.save()
예제 #6
0
class Command(BaseCommand):
    help = 'Send out all unsent tasks'
    TELEGRAM_BOT = Bot()

    @print_sql_decorator()
    @transaction.atomic
    def handle(self, *args, **options):
        logger = getLogger('notification.sendout.tasks')

        delete_info = Task.objects.filter(is_sent=True,
                                          modified__lte=now() -
                                          timedelta(days=31)).delete()
        logger.info(f'Tasks cleared: {delete_info}')

        qs = Task.unsent.all()
        qs = qs.select_related('notification__coder')
        qs = qs.prefetch_related(
            Prefetch(
                'notification__coder__chat_set',
                queryset=Chat.objects.filter(is_group=False),
                to_attr='cchat',
            ))

        done = 0
        failed = 0
        for task in tqdm.tqdm(qs.iterator(), 'sending'):
            try:
                task.is_sent = True
                notification = task.notification
                coder = notification.coder
                method, *args = notification.method.split(':', 1)
                if method == Notification.TELEGRAM:
                    will_mail = False

                    if args:
                        self.TELEGRAM_BOT.send_message(task.message, args[0])
                    elif coder.chat and coder.chat.chat_id:
                        try:
                            if not coder.settings.get('telegram', {}).get(
                                    'unauthorized', False):
                                self.TELEGRAM_BOT.send_message(
                                    task.message, coder.chat.chat_id)
                        except Unauthorized:
                            coder.settings.setdefault(
                                'telegram', {})['unauthorized'] = True
                            coder.save()
                            will_mail = True
                    else:
                        will_mail = True

                    if will_mail:
                        pass
                        # FIXME: skipping, fixed on https://yandex.ru/support/mail-new/web/spam/honest-mailers.html
                        # send_mail(
                        #     settings.EMAIL_PREFIX_SUBJECT_ + task.subject,
                        #     '%s, connect telegram chat by link %s.' % (
                        #         coder.user.username,
                        #         settings.HTTPS_HOST_ + reverse('telegram:me')
                        #     ),
                        #     'Contest list <*****@*****.**>',
                        #     [coder.user.email],
                        #     fail_silently=False,
                        # )
                elif method == Notification.EMAIL:
                    pass
                    # FIXME: skipping, fixed on https://yandex.ru/support/mail-new/web/spam/honest-mailers.html
                    # send_mail(
                    #     settings.EMAIL_PREFIX_SUBJECT_ + task.subject,
                    #     task.addition['text'],
                    #     'Contest list <*****@*****.**>',
                    #     [coder.user.email],
                    #     fail_silently=False,
                    #     html_message=task.message,
                    # )
            except Exception:
                logger.error('Exception sendout task:\n%s' % format_exc())
                task.is_sent = False
            if task.is_sent:
                done += 1
            else:
                failed += 1
            task.save()
        logger.info(f'Done: {done}, failed: {failed}')
예제 #7
0
 def __init__(self):
     self._bot = Bot()
예제 #8
0
    def handle(self, *args, **options):
        self.stdout.write(str(options))
        args = AttrDict(options)

        bot = Bot()

        if args.dump is not None and os.path.exists(args.dump):
            with open(args.dump, 'r') as fo:
                standings = json.load(fo)
        else:
            standings = {}

        problems_info = standings.setdefault('__problems_info', {})

        parser_command = ParserCommand()

        iteration = 1 if args.dump else 0
        while True:
            subprocess.call('clear', shell=True)
            print(now())

            contest = Contest.objects.filter(pk=args.cid)
            parser_command.parse_statistic(contest,
                                           without_contest_filter=True)
            contest = contest.first()
            resource = contest.resource
            statistics = list(Statistics.objects.filter(contest=contest))

            for p in problems_info.values():
                if p.get('accepted') or not p.get('n_hidden'):
                    p.pop('show_hidden', None)
                p['n_hidden'] = 0

            updated = False
            has_hidden = False
            numbered = 0
            for stat in sorted(statistics, key=lambda s: s.place_as_int):
                name_instead_key = resource.info.get(
                    'standings', {}).get('name_instead_key')
                name_instead_key = stat.account.info.get(
                    '_name_instead_key', name_instead_key)

                if name_instead_key:
                    name = stat.account.name
                else:
                    name = stat.addition.get('name')
                    if not name or not has_season(stat.account.key, name):
                        name = stat.account.key

                filtered = False
                if args.query is not None and re.search(
                        args.query, name, re.I):
                    filtered = True

                message_id = None
                key = str(stat.account.id)
                if key in standings:
                    if filtered:
                        print(stat.modified, {
                            k: v
                            for k, v in standings[key].items()
                            if k != 'problems'
                        },
                              end=' | ')
                        print(stat.place, stat.solving, end=' | ')
                    problems = standings[key]['problems']
                    message_id = standings[key].get('messageId')

                    def delete_message():
                        nonlocal message_id
                        if message_id:
                            for iteration in range(1, 5):
                                try:
                                    bot.delete_message(chat_id=args.tid,
                                                       message_id=message_id)
                                    message_id = None
                                    break
                                except telegram.error.TimedOut as e:
                                    logger.warning(str(e))
                                    time.sleep(iteration)
                                    continue

                    p = []
                    has_update = False
                    has_first_ac = False
                    has_try_first_ac = False
                    has_new_accepted = False

                    for k, v in stat.addition.get('problems', {}).items():
                        p_info = problems_info.setdefault(k, {})
                        p_result = problems.get(k, {}).get('result')
                        result = v['result']

                        is_hidden = str(result).startswith('?')
                        is_accepted = str(result).startswith('+')
                        try:
                            is_accepted = is_accepted or float(
                                result) > 0 and not v.get('partial')
                        except Exception:
                            pass

                        if is_hidden:
                            p_info['n_hidden'] = p_info.get('n_hidden', 0) + 1

                        if p_result != result or is_hidden:
                            has_new_accepted |= is_accepted
                            m = '%s%s %s' % (k, ('. ' + v['name'])
                                             if 'name' in v else '', result)

                            if v.get('verdict'):
                                m += ' ' + v['verdict']

                            if p_result != result:
                                m = '*%s*' % m
                                has_update = True
                                if iteration:
                                    if p_info.get('show_hidden') == key:
                                        delete_message()
                                        if not is_hidden:
                                            p_info.pop('show_hidden')
                                    if not p_info.get('accepted'):
                                        if is_accepted:
                                            m += ' FIRST ACCEPTED'
                                            has_first_ac = True
                                        elif is_hidden and not p_info.get(
                                                'show_hidden'):
                                            p_info['show_hidden'] = key
                                            m += ' TRY FIRST AC'
                                            has_try_first_ac = True
                                if args.top and stat.place_as_int <= args.top:
                                    if not filtered:
                                        m += f' TOP{args.top}'
                                    filtered = True
                            p.append(m)
                        if result.startswith('+'):
                            p_info['accepted'] = True
                        has_hidden = has_hidden or is_hidden

                    prev_place = standings[key].get('place')
                    place = stat.place
                    if has_new_accepted and prev_place:
                        place = '%s->%s' % (prev_place, place)
                    if args.numbered is not None and re.search(
                            args.numbered, stat.account.key, re.I):
                        numbered += 1
                        place = '%s (%s)' % (place, numbered)

                    msg = '%s. _%s_' % (place,
                                        telegram.utils.helpers.escape_markdown(
                                            name.replace('_', ' ')))
                    if p:
                        msg = '%s, %s' % (', '.join(p), msg)
                    if abs(standings[key]['solving'] - stat.solving) > 1e-9:
                        msg += ' = %d' % stat.solving
                        if 'penalty' in stat.addition:
                            msg += f' ({stat.addition["penalty"]})'

                    if has_update or has_first_ac or has_try_first_ac:
                        updated = True

                    if filtered:
                        print(stat.place, stat.solving, end=' | ')

                    if filtered:
                        print(msg)

                    if filtered and has_update or has_first_ac or has_try_first_ac:
                        if not args.dryrun:
                            delete_message()
                            for iteration in range(1, 5):
                                try:
                                    message = bot.send_message(
                                        msg=msg, chat_id=args.tid)
                                    message_id = message.message_id
                                    break
                                except telegram.error.TimedOut as e:
                                    logger.warning(str(e))
                                    time.sleep(iteration * 3)
                                    continue
                                except telegram.error.BadRequest as e:
                                    logger.error(str(e))
                                    break

                standings[key] = {
                    'solving': stat.solving,
                    'place': stat.place,
                    'problems': stat.addition.get('problems', {}),
                    'messageId': message_id,
                }
            if args.dump is not None and (updated
                                          or not os.path.exists(args.dump)):
                standings_dump = json.dumps(standings, indent=2)
                with open(args.dump, 'w') as fo:
                    fo.write(standings_dump)

            if iteration:
                is_over = contest.end_time < now()
                if is_over and not has_hidden:
                    break
                tick = 60 if is_over else 1
                limit = now() + timedelta(seconds=args.delay * tick)
                size = 1
                while now() < limit:
                    value = humanize.naturaldelta(limit - now())
                    out = f'{value:{size}s}'
                    size = len(value)
                    print(out, end='\r')
                    time.sleep(tick)
                print()

            iteration += 1
예제 #9
0
class Command(BaseCommand):
    help = 'Send out all unsent tasks'
    TELEGRAM_BOT = Bot()

    def add_arguments(self, parser):
        parser.add_argument('--coders', nargs='+')
        parser.add_argument('--dryrun', action='store_true', default=False)

    def get_message(self, method, data, **kwargs):
        subject_ = kwargs.pop('subject', None)
        message_ = kwargs.pop('message', None)

        if 'contests' in data:
            contests = Contest.objects.filter(pk__in=data['contests'])
            context = deepcopy(data.get('context', {}))
            context.update({
                'contests': contests,
                'domain': settings.HTTPS_HOST_,
            })
            context.update(kwargs)
            subject = render_to_string('subject', context).strip()
            context['subject'] = subject
            method = method.split(':', 1)[0]
            message = render_to_string('message/%s' % method, context).strip()
        else:
            subject = ''
            message = ''
            context = {}

        if subject_:
            subject = subject_ + subject

        if message_:
            message = message_ + message

        return subject, message, context

    def send_message(self, coder, method, data, **kwargs):
        method, *args = method.split(':', 1)
        subject, message, context = self.get_message(method=method, data=data, coder=coder,  **kwargs)
        if method == settings.NOTIFICATION_CONF.TELEGRAM:
            if args:
                self.TELEGRAM_BOT.send_message(message, args[0], reply_markup=False)
            elif coder.chat and coder.chat.chat_id:
                try:
                    if not coder.settings.get('telegram', {}).get('unauthorized', False):
                        self.TELEGRAM_BOT.send_message(message, coder.chat.chat_id, reply_markup=False)
                except Unauthorized as e:
                    if 'bot was blocked by the user' in str(e):
                        coder.chat.delete()
                    else:
                        coder.settings.setdefault('telegram', {})['unauthorized'] = True
                        coder.save()
            elif 'notification' in kwargs:
                kwargs['notification'].delete()
        elif method == settings.NOTIFICATION_CONF.EMAIL:
            send_mail(
                subject,
                message,
                'CLIST <*****@*****.**>',
                [coder.user.email],
                fail_silently=False,
                html_message=message,
            )
        elif method == settings.NOTIFICATION_CONF.WEBBROWSER:
            payload = {
                'head': subject,
                'body': message,
            }
            contests = list(context.get('contests', []))
            if len(contests) == 1:
                contest = contests[0]
                payload['url'] = contest.url
                payload['icon'] = f'{settings.HTTPS_HOST_}/imagefit/static_resize/64x64/{contest.resource.icon}'

            send_user_notification(
                user=coder.user,
                payload=payload,
                ttl=300,
            )

    @print_sql_decorator()
    @transaction.atomic
    def handle(self, *args, **options):
        coders = options.get('coders')
        dryrun = options.get('dryrun')

        logger = getLogger('notification.sendout.tasks')

        delete_info = Task.objects.filter(
            Q(is_sent=True, modified__lte=now() - timedelta(days=1)) |
            Q(modified__lte=now() - timedelta(days=7))
        ).delete()
        logger.info(f'Tasks cleared: {delete_info}')

        if dryrun:
            qs = Task.objects.all()
        else:
            qs = Task.unsent.all()
        qs = qs.select_related('notification__coder')
        qs = qs.prefetch_related(
            Prefetch(
                'notification__coder__chat_set',
                queryset=Chat.objects.filter(is_group=False),
                to_attr='cchat',
            )
        )
        if settings.STOP_EMAIL_:
            qs = qs.exclude(notification__method='email')

        if coders:
            qs = qs.filter(notification__coder__username__in=coders)

        if dryrun:
            qs = qs.order_by('-modified')[:1]

        done = 0
        failed = 0
        stop_email = settings.STOP_EMAIL_
        for task in tqdm.tqdm(qs.iterator(), 'sending'):
            if stop_email and task.notification.method == settings.NOTIFICATION_CONF.EMAIL:
                continue

            try:
                task.is_sent = True
                task.save()
                notification = task.notification
                coder = notification.coder
                method = notification.method

                self.send_message(
                    coder,
                    method,
                    task.addition,
                    subject=task.subject,
                    message=task.message,
                    notification=notification,
                )
            except Exception as e:
                logger.error('Exception sendout task:\n%s' % format_exc())
                task.is_sent = False
                task.save()
                if isinstance(e, SMTPResponseException):
                    stop_email = True
            if task.is_sent:
                done += 1
            else:
                failed += 1
        logger.info(f'Done: {done}, failed: {failed}')
예제 #10
0
    def handle(self, *args, **options):
        self.stdout.write(str(options))
        args = AttrDict(options)

        bot = Bot()

        if args.dump is not None and os.path.exists(args.dump):
            with open(args.dump, 'r') as fo:
                standings = json.load(fo)
        else:
            standings = {}

        problems_info = standings.setdefault('__problems_info', {})

        parser_command = ParserCommand()

        iteration = 1 if args.dump else 0
        while True:
            subprocess.call('clear', shell=True)

            contest = Contest.objects.get(pk=args.cid)
            parser_command.parse_statistic([contest], with_check=False)
            statistics = Statistics.objects.filter(contest=contest)

            updated = False
            has_hidden = False
            numbered = 0
            for stat in sorted(statistics, key=lambda s: s.place_as_int):
                filtered = False
                if args.query is not None and re.search(
                        args.query, stat.account.key, re.I):
                    filtered = True

                message_id = None
                key = str(stat.account.id)
                if key in standings:
                    problems = standings[key]['problems']
                    message_id = standings[key].get('messageId')
                    p = []
                    has_update = False
                    has_first_ac = False
                    for k, v in stat.addition.get('problems', {}).items():
                        p_info = problems_info.setdefault(k, {})
                        p_result = problems.get(k, {}).get('result')
                        result = v['result']
                        is_hidden = result.startswith('?')
                        try:
                            is_accepted = result.startswith('+')
                            is_accepted = is_accepted or float(result) > 0
                        except Exception:
                            pass
                        if p_result != result or is_hidden:
                            m = '%s%s %s' % (k, ('. ' + v['name'])
                                             if 'name' in v else '', result)

                            if p_result != result:
                                m = '*%s*' % m
                                has_update = True
                                if iteration and is_accepted and not p_info.get(
                                        'accepted'):
                                    m += ' FIRST ACCEPTED'
                                    has_first_ac = True
                                if is_accepted and args.top and stat.place_as_int <= args.top:
                                    filtered = True
                            p.append(m)
                        if result.startswith('+'):
                            p_info['accepted'] = True
                        has_hidden = has_hidden or is_hidden

                    if args.numbered is not None and re.search(
                            args.numbered, stat.account.key, re.I):
                        numbered += 1
                        place = '%s (%s)' % (stat.place, numbered)
                    else:
                        place = stat.place

                    msg = '%s. _%s_ %s' % (place, stat.account.key,
                                           ', '.join(p))
                    if standings[key]['solving'] != stat.solving:
                        msg += ' = %d' % stat.solving

                    if has_update or has_first_ac:
                        updated = True

                    if filtered:
                        print(msg)

                    if filtered and has_update or has_first_ac:
                        if not args.dryrun:
                            for _ in range(1, 5):
                                try:
                                    if message_id:
                                        bot.delete_message(
                                            chat_id=args.tid,
                                            message_id=message_id)
                                    message = bot.send_message(
                                        msg=msg, chat_id=args.tid)
                                    message_id = message.message_id
                                except telegram.error.TimedOut as e:
                                    logger.warning(str(e))
                                    time.sleep(_ * 3)
                                    continue
                                except telegram.error.BadRequest as e:
                                    logger.error(str(e))
                                    break

                standings[key] = {
                    'solving': stat.solving,
                    'problems': stat.addition.get('problems', {}),
                    'messageId': message_id,
                }
            if args.dump is not None and (updated
                                          or not os.path.exists(args.dump)):
                standings_dump = json.dumps(standings, indent=2)
                with open(args.dump, 'w') as fo:
                    fo.write(standings_dump)

            if iteration:
                is_over = contest.end_time < now()
                if is_over and not has_hidden:
                    break
                time.sleep(args.delay * (60 if is_over else 1))

            iteration += 1