1# Copyright (c) 2012-2019 Seafile Ltd.
2# encoding: utf-8
3from datetime import datetime
4import logging
5import re
6import requests
7import json
8
9from django.core.management.base import BaseCommand
10from django.urls import reverse
11from django.utils import translation
12from django.utils.translation import ungettext
13
14from seahub.base.models import CommandsLastCheck
15from seahub.notifications.models import UserNotification
16from seahub.utils import get_site_scheme_and_netloc, get_site_name
17from seahub.auth.models import SocialAuthUser
18
19from seahub.dingtalk.utils import dingtalk_get_access_token, dingtalk_get_userid_by_unionid
20from seahub.dingtalk.settings import DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL, \
21        DINGTALK_AGENT_ID
22
23# Get an instance of a logger
24logger = logging.getLogger(__name__)
25
26
27# https://ding-doc.dingtalk.com/doc#/serverapi3/wvdxel
28
29########## Utility Functions ##########
30def remove_html_a_element(s):
31    """
32    Replace <a ..>xx</a> to xx and wrap content with <div></div>.
33    """
34    patt = '<a.*?>(.+?)</a>'
35
36    def repl(matchobj):
37        return matchobj.group(1)
38
39    return re.sub(patt, repl, s)
40
41
42class CommandLogMixin(object):
43
44    def println(self, msg):
45        self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
46
47    def log_error(self, msg):
48        logger.error(msg)
49        self.println(msg)
50
51    def log_info(self, msg):
52        logger.info(msg)
53        self.println(msg)
54
55    def log_debug(self, msg):
56        logger.debug(msg)
57        self.println(msg)
58
59
60#######################################
61
62class Command(BaseCommand, CommandLogMixin):
63    """ send dingtalk notifications
64    """
65
66    help = 'Send dingtalk msg to user if he/she has unseen notices every '
67    'period of time.'
68    label = "notifications_send_dingtalk_notices"
69
70    def handle(self, *args, **options):
71        self.log_debug('Start sending dingtalk msg...')
72        self.do_action()
73        self.log_debug('Finish sending dingtalk msg.\n')
74
75    def send_dingtalk_msg(self, user_id, title, content):
76
77        self.log_info('Send dingtalk msg to user: %s, msg: %s' % (user_id, content))
78        data = {
79            "agent_id": DINGTALK_AGENT_ID,
80            "userid_list": user_id,
81            "msg": {
82                "msgtype": "markdown",
83                "markdown": {
84                    "title": title,
85                    "text": content
86                }
87            }
88        }
89        resp_json = requests.post(self.dingtalk_message_send_to_conversation_url,
90                data=json.dumps(data)).json()
91        if resp_json.get('errcode') != 0:
92            self.log_info(resp_json)
93
94    def do_action(self):
95
96        # check before start
97        access_token = dingtalk_get_access_token()
98        if not access_token:
99            self.log_error('can not get access_token')
100
101        self.dingtalk_message_send_to_conversation_url = DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL + '?access_token=' + access_token
102        self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
103        site_name = get_site_name()
104
105        # start
106        now = datetime.now()
107        today = datetime.now().replace(hour=0).replace(minute=0).replace(
108            second=0).replace(microsecond=0)
109
110        # 1. get all users who are connected dingtalk
111        socials = SocialAuthUser.objects.filter(provider='dingtalk')
112        users = [(x.username, x.uid) for x in socials]
113        self.log_info('Found %d users' % len(users))
114        if not users:
115            return
116
117        user_uid_map = {}
118        for username, uid in users:
119            user_uid_map[username] = dingtalk_get_userid_by_unionid(uid)
120
121        # 2. get previous time that command last runs
122        try:
123            cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
124            self.log_debug('Last check time is %s' % cmd_last_check.last_check)
125
126            last_check_dt = cmd_last_check.last_check
127
128            cmd_last_check.last_check = now
129            cmd_last_check.save()
130        except CommandsLastCheck.DoesNotExist:
131            last_check_dt = today
132            self.log_debug('Create new last check time: %s' % now)
133            CommandsLastCheck(command_type=self.label, last_check=now).save()
134
135        # 3. get all unseen notices for those users
136        qs = UserNotification.objects.filter(
137            timestamp__gt=last_check_dt
138        ).filter(seen=False).filter(
139            to_user__in=list(user_uid_map.keys())
140        )
141        self.log_info('Found %d notices' % qs.count())
142        if qs.count() == 0:
143            return
144
145        user_notices = {}
146        for q in qs:
147            if q.to_user not in user_notices:
148                user_notices[q.to_user] = [q]
149            else:
150                user_notices[q.to_user].append(q)
151
152        # save current language
153        cur_language = translation.get_language()
154        # active zh-cn
155        translation.activate('zh-cn')
156        self.log_info('the language is set to zh-cn')
157
158        # 4. send msg to users
159        for username, uid in users:
160            user_id = user_uid_map[username]
161            notices = user_notices.get(username, [])
162            count = len(notices)
163            if count == 0:
164                continue
165
166            title = ungettext(
167                "\n"
168                "You've got 1 new notice on %(site_name)s:\n",
169                "\n"
170                "You've got %(num)s new notices on %(site_name)s:\n",
171                count
172            ) % {'num': count, 'site_name': site_name, }
173
174            content = '  \n  '.join([remove_html_a_element(x.format_msg()) for x in notices])
175            self.send_dingtalk_msg(user_id, title, content)
176
177        # reset language
178        translation.activate(cur_language)
179        self.log_info('reset language success')
180