from datetime import timedelta, timezone from django.conf import settings from django.utils import timezone as django_timezone from django.db import models from celery.utils.log import get_task_logger from pingcap_sso.celery import app as celery_app from pingcap_sso.discourse.models import ( DiscourseTopic, DiscourseGroupUser, DiscourseUser, DiscourseUserBadge, DiscourseTopicTag, DiscourseTopicCustomField, DiscourseCategory ) from pingcap_sso.discourse.consts import QA_CATEGORIES from pingcap_sso.models import ( LarkMessage, AskTUGTopicNotifyHistory, ) from pingcap_sso.lib.user import user_profile_for_marketing from pingcap_sso.discourse.models import DiscourseTopicCustomField from pingcap_sso.utils import time as ptime from pingcap_sso.discourse import api as discourse_api logger = get_task_logger(__name__) @celery_app.task def grant_badge_to_users( usernames, badge_id, replace_badge_owners=False, grant_existing_holders=False): return discourse_api.grant_badge_to_users( usernames, badge_id, replace_badge_owners, grant_existing_holders) @celery_app.task def revoke_user_badge(username, badge_id): try: user_badge = DiscourseUserBadge.objects.get( user__username=username, badge_id=badge_id) return discourse_api.revoke_user_badge(user_badge.id) except DiscourseUserBadge.DoesNotExist: return True @celery_app.task def set_flair_group_for_user(username, flair_group_id): return discourse_api.set_flair_group_for_user(username, flair_group_id) @celery_app.task def add_users_to_group(group_id, usernames, notify_users=False, ignore_422_error=True): return discourse_api.add_users_to_group(group_id, usernames, notify_users, ignore_422_error) @celery_app.task def remove_user_from_group(group_id, user_id): return discourse_api.remove_user_from_group(group_id, user_id) @celery_app.task def send_private_message(usernames, title, raw_message): return discourse_api.send_private_message(usernames, title, raw_message) @celery_app.task def mark_user_as_verified(username): if not DiscourseGroupUser.objects.filter( user__username=username, group_id=settings.DISCOURSE_VERIFIED_GROUP_ID).exists(): discourse_api.add_users_to_group( settings.DISCOURSE_VERIFIED_GROUP_ID, [username]) discourse_api.set_flair_group_for_user( username, settings.DISCOURSE_VERIFIED_GROUP_ID) @celery_app.task def mark_user_as_unverified(username): user = DiscourseUser.objects.get(username=username) if DiscourseGroupUser.objects.filter(user=user).exists(): discourse_api.remove_user_from_group( settings.DISCOURSE_VERIFIED_GROUP_ID, user.id) if user.flair_group_id == settings.DISCOURSE_VERIFIED_GROUP_ID: # flair_group_id can only be clear if args is an empty string, # `None` wont work discourse_api.set_flair_group_for_user(username, '') @celery_app.task def notify_new_topic_by_categories(category_ids, task_name, lark_webhook, lark_message_remark=None): # task_name: used to record the notify history, prevent duplicate notify, should be unique in each task # lark_webhook: notify webhook url in lark if not lark_message_remark: lark_message_remark = task_name now = django_timezone.now().astimezone( timezone(timedelta(hours=8)) ) _60_day_ago = now - timedelta(days=60) topics = DiscourseTopic.objects.filter( category_id__in=category_ids, user_id__gt=0, created_at__gte=_60_day_ago, deleted_at__isnull=True, ) _send_topics_to_lark(topics, task_name, lark_webhook, lark_message_remark) @celery_app.task def notify_new_topic_by_tags(tag_ids, task_name, lark_webhook, lark_message_remark=None): # task_name: used to record the notify history, prevent duplicate notify, should be unique in each task # lark_webhook: notify webhook url in lark if not lark_message_remark: lark_message_remark = task_name now = django_timezone.now().astimezone( timezone(timedelta(hours=8)) ) _60_day_ago = now - timedelta(days=60) topics = DiscourseTopic.objects.filter( id__in=DiscourseTopicTag.objects.filter( tag_id__in=tag_ids ).values_list('topic_id', flat=True), created_at__gte=_60_day_ago, deleted_at__isnull=True, ) _send_topics_to_lark(topics, task_name, lark_webhook, lark_message_remark) def _send_topics_to_lark(topics, task_name, lark_webhook, lark_message_remark): for topic in topics: deliver_history, created = AskTUGTopicNotifyHistory.objects.get_or_create( topic_id=topic.id, task_name=task_name ) # Already delivered if not created: continue sso_user = topic.user.get_sso_user() user_profile = user_profile_for_marketing(sso_user) author_profile_link = f'https://accounts.pingcap.cn/manage#/users/{ sso_user.username }/info' topic_link = f'https://asktug.com/t/topic/{ topic.id }' deliver_history.notify_success = LarkMessage.send( webhook=lark_webhook, message_type=LarkMessage.MESSAGE_TYPE_POST, content={ 'title': '', 'content': [ [ {'tag': 'text', 'text': '用户名: '}, {'tag': 'a', 'text': sso_user.username, 'href': author_profile_link} ], [ {'tag': 'text', 'text': '公司: '}, {'tag': 'text', 'text': user_profile.get('company') or '无'}, ], [ {'tag': 'text', 'text': '链接: '}, {'tag': 'a', 'text': topic.title, 'href': topic_link} ] ] }, remark=lark_message_remark, _async=False ) deliver_history.save() @celery_app.task def notify_qa_topics_72h_noreply(lark_webhook): task_name = 'qa_topic_72_hour_noreply' now = django_timezone.now().astimezone( timezone(timedelta(hours=8)) ) _72_hour_ago = now - timedelta(hours=72) _60_day_ago = now - timedelta(days=60) solved_topics_ids = DiscourseTopicCustomField.objects.filter( name='accepted_answer_post_id', value__isnull=False, topic_id__isnull=False ).values_list('topic_id', flat=True) category_desc_topic_ids = DiscourseCategory.objects.filter( topic_id__isnull=False ).values_list('topic_id', flat=True) noreply_topics = DiscourseTopic.objects.filter( created_at__lt=_72_hour_ago, created_at__gte=_60_day_ago, participant_count=1, category_id__in=QA_CATEGORIES, deleted_at__isnull=True, visible=1, closed=0, ).exclude( id__in=solved_topics_ids ).exclude( id__in=category_desc_topic_ids ) for topic in noreply_topics: deliver_history, created = AskTUGTopicNotifyHistory.objects.get_or_create( topic_id=topic.id, task_name=task_name ) # Already delivered if not created: continue sso_user = topic.user.get_sso_user() user_profile = user_profile_for_marketing(sso_user) author_profile_link = f'https://accounts.pingcap.cn/manage#/users/{ sso_user.username }/info' topic_link = f'https://asktug.com/t/topic/{ topic.id }' deliver_history.notify_success = LarkMessage.send( webhook=lark_webhook, message_type=LarkMessage.MESSAGE_TYPE_POST, content={ 'title': '', 'content': [ [ {'tag': 'text', 'text': '发帖用户: '}, {'tag': 'a', 'text': sso_user.username, 'href': author_profile_link} ], [ {'tag': 'text', 'text': '公司名称: '}, {'tag': 'text', 'text': user_profile.get('company') or '无'}, ], [ {'tag': 'text', 'text': '链接: '}, {'tag': 'a', 'text': topic.title, 'href': topic_link} ], [ {'tag': 'text', 'text': '发帖时间: '}, {'tag': 'text', 'text': ptime.local_time_format(topic.created_at, '%Y/%m/%d %H:%M')} ], [ {'tag': 'a', 'text': '更多', 'href': 'https://meta.pingcap.net/question/8204'} ] ] }, remark='AskTUG 问答板块 72 小时内没有回复的问题', _async=False ) deliver_history.save() def timedelta_except_weekend(start_at, end_at): diff = timedelta(hours=0, minutes=0) while start_at < end_at: next_day = start_at.replace(hour=0, minute=0, microsecond=0) + timedelta(days=1) if start_at.isoweekday() < 6: if start_at.date() == end_at.date(): diff += (end_at - start_at) return diff else: diff += (next_day - start_at) start_at = next_day return diff @celery_app.task def notify_qa_topics_72h_unresolved(lark_webhook): """ 告警规则: 1、技术问题贴 2、帖子创建超过 72 h 未选择【对我有用】扣减周末时间的 48h 3、最后发言人为发帖者 4、帖子中包含除发帖人之外的用户回复 (剔除 0 回复, 剔除除发帖子以外无人回复的帖子, 此类内容会在 72h 无回复中爆出) """ task_name = 'qa_topic_72_hour_unresolved' now = django_timezone.now().astimezone( timezone(timedelta(hours=8)) ) _72_hour_ago = now - timedelta(hours=72) _60_day_agp = now - timedelta(days=60) solved_topics_ids = DiscourseTopicCustomField.objects.filter( name='accepted_answer_post_id', value__isnull=False, topic_id__isnull=False ).values_list('topic_id', flat=True) category_desc_topic_ids = DiscourseCategory.objects.filter( topic_id__isnull=False ).values_list('topic_id', flat=True) unresolved_topics = DiscourseTopic.objects.filter( created_at__lt=_72_hour_ago, created_at__gte=_60_day_agp, last_post_user_id=models.F('user_id'), participant_count__gt=1, category_id__in=QA_CATEGORIES, deleted_at__isnull=True, visible=1, closed=0, ).exclude( id__in=solved_topics_ids ).exclude( id__in=category_desc_topic_ids ) for topic in unresolved_topics: created_at = topic.created_at.astimezone( timezone(timedelta(hours=8)) ) if timedelta_except_weekend(created_at, now) < timedelta(hours=72): continue deliver_history, created = AskTUGTopicNotifyHistory.objects.get_or_create( topic_id=topic.id, task_name=task_name ) # Already delivered if not created: continue sso_user = topic.user.get_sso_user() user_profile = user_profile_for_marketing(sso_user) author_profile_link = f'https://accounts.pingcap.cn/manage#/users/{ sso_user.username }/info' topic_link = f'https://asktug.com/t/topic/{ topic.id }' deliver_history.notify_success = LarkMessage.send( webhook=lark_webhook, message_type=LarkMessage.MESSAGE_TYPE_POST, content={ 'title': '', 'content': [ [ {'tag': 'text', 'text': '发帖用户: '}, {'tag': 'a', 'text': sso_user.username, 'href': author_profile_link} ], [ {'tag': 'text', 'text': '公司名称: '}, {'tag': 'text', 'text': user_profile.get('company') or '无'}, ], [ {'tag': 'text', 'text': '链接: '}, {'tag': 'a', 'text': topic.title, 'href': topic_link} ], [ {'tag': 'text', 'text': '发帖时间: '}, {'tag': 'text', 'text': ptime.local_time_format(topic.created_at, '%Y/%m/%d %H:%M')} ], [ {'tag': 'a', 'text': '更多', 'href': 'https://meta.pingcap.net/question/8204'} ] ] }, remark='AskTUG 问答板块 72 小时未解决的问题', _async=False ) deliver_history.save()