1# Copyright (c) 2012-2019 Seafile Ltd.
2# encoding: utf-8
3from datetime import datetime
4import logging
5import re
6import json
7import requests
8
9from django.core.management.base import BaseCommand
10from django.urls import reverse
11from django.utils import translation
12from django.utils.translation import ugettext as _
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, ENABLE_DINGTALK
22
23from seahub.work_weixin.utils import get_work_weixin_access_token, handler_work_weixin_api_response
24from seahub.work_weixin.settings import WORK_WEIXIN_NOTIFICATIONS_URL, \
25    WORK_WEIXIN_PROVIDER, WORK_WEIXIN_UID_PREFIX, WORK_WEIXIN_AGENT_ID, ENABLE_WORK_WEIXIN
26
27# Get an instance of a logger
28logger = logging.getLogger(__name__)
29
30########## Utility Functions ##########
31
32# https://ding-doc.dingtalk.com/doc#/serverapi3/wvdxel
33def remove_html_a_element_for_dingtalk(s):
34    """
35    Replace <a ..>xx</a> to xx and wrap content with <div></div>.
36    """
37    patt = '<a.*?>(.+?)</a>'
38
39    def repl(matchobj):
40        return matchobj.group(1)
41
42    return re.sub(patt, repl, s)
43
44# https://work.weixin.qq.com/api/doc#90000/90135/90236/
45def wrap_div_for_work_weixin(s):
46    """
47    Replace <a ..>xx</a> to xx and wrap content with <div></div>.
48    """
49    patt = '<a.*?>(.+?)</a>'
50
51    def repl(matchobj):
52        return matchobj.group(1)
53
54    return '<div class="highlight">' + re.sub(patt, repl, s) + '</div>'
55
56class CommandLogMixin(object):
57
58    def println(self, msg):
59        self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
60
61    def log_error(self, msg):
62        logger.error(msg)
63        self.println(msg)
64
65    def log_info(self, msg):
66        logger.info(msg)
67        self.println(msg)
68
69    def log_debug(self, msg):
70        logger.debug(msg)
71        self.println(msg)
72
73
74class Command(BaseCommand, CommandLogMixin):
75    """ send dingtalk/work weixin notifications
76    """
77
78    help = "Send notices to user's social account if he/she has unseen notices every period of time."
79    label = "notifications_send_notices_to_social_account"
80
81    def handle(self, *args, **options):
82
83        if ENABLE_DINGTALK:
84            self.log_debug('Start sending dingtalk msg...')
85        if ENABLE_WORK_WEIXIN:
86            self.log_debug('Start sending work weixin msg...')
87
88        self.do_action()
89
90        if ENABLE_DINGTALK:
91            self.log_debug('Finish sending dingtalk msg.\n')
92        if ENABLE_WORK_WEIXIN:
93            self.log_debug('Finish sending work weixin msg.\n')
94
95    def send_dingtalk_msg(self, user_id, title, content):
96
97        self.log_info('Send dingtalk msg to user: %s, msg: %s' % (user_id, content))
98        data = {
99            "agent_id": DINGTALK_AGENT_ID,
100            "userid_list": user_id,
101            "msg": {
102                "msgtype": "markdown",
103                "markdown": {
104                    "title": title,
105                    "text": content
106                }
107            }
108        }
109        resp_json = requests.post(self.dingtalk_message_send_to_conversation_url,
110                data=json.dumps(data)).json()
111        if resp_json.get('errcode') != 0:
112            self.log_info(resp_json)
113
114    def send_work_weixin_msg(self, uid, title, content):
115
116        self.log_info('Send wechat msg to user: %s, msg: %s' % (uid, content))
117
118        data = {
119            "touser": uid,
120            "agentid": WORK_WEIXIN_AGENT_ID,
121            'msgtype': 'textcard',
122            'textcard': {
123                'title': title,
124                'description': content,
125                'url': self.detail_url,
126            },
127        }
128
129        api_response = requests.post(self.work_weixin_notifications_url, json=data)
130        api_response_dic = handler_work_weixin_api_response(api_response)
131        if api_response_dic:
132            self.log_info(api_response_dic)
133        else:
134            self.log_error('can not get work weixin notifications API response')
135
136    def do_action(self):
137
138        if not ENABLE_DINGTALK and not ENABLE_WORK_WEIXIN:
139            self.log_info('No social account enabled')
140            return
141
142        dingtalk_access_token = ''
143        work_weixin_access_token = ''
144
145        if ENABLE_DINGTALK:
146
147            dingtalk_access_token = dingtalk_get_access_token()
148            if not dingtalk_access_token:
149                self.log_error('can not get access token for dingtalk')
150            else:
151                self.dingtalk_message_send_to_conversation_url = DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL + \
152                        '?access_token=' + dingtalk_access_token
153                self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
154
155        if ENABLE_WORK_WEIXIN:
156
157            work_weixin_access_token = get_work_weixin_access_token()
158            if not work_weixin_access_token:
159                self.log_error('can not get access token for work weixin')
160            else:
161                self.work_weixin_notifications_url = WORK_WEIXIN_NOTIFICATIONS_URL + \
162                        '?access_token=' + work_weixin_access_token
163                self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
164
165        if not dingtalk_access_token and not work_weixin_access_token:
166            return
167
168        # save current language
169        cur_language = translation.get_language()
170        # active zh-cn
171        translation.activate('zh-cn')
172        self.log_info('the language is set to zh-cn')
173
174        # 1. get previous time that command last runs
175        now = datetime.now()
176        today = datetime.now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)
177
178        try:
179            cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
180            self.log_debug('Last check time is %s' % cmd_last_check.last_check)
181
182            last_check_dt = cmd_last_check.last_check
183
184            cmd_last_check.last_check = now
185            cmd_last_check.save()
186        except CommandsLastCheck.DoesNotExist:
187            last_check_dt = today
188            self.log_debug('Create new last check time: %s' % now)
189            CommandsLastCheck(command_type=self.label, last_check=now).save()
190
191        # 2. get all unseen notices
192        user_notifications = UserNotification.objects.filter(timestamp__gt=last_check_dt).filter(seen=False)
193        self.log_info('Found %d notices' % user_notifications.count())
194        if user_notifications.count() == 0:
195            return
196
197        # 3. get all users should send notice to
198        user_email_list = list(set([item.to_user for item in user_notifications]))
199
200        dingtail_socials = SocialAuthUser.objects.filter(provider='dingtalk').filter(username__in=user_email_list)
201        dingtalk_email_list = [item.username for item in dingtail_socials]
202        dingtalk_email_uid_dict = {}
203        for item in dingtail_socials:
204            dingtalk_email_uid_dict[item.username] = dingtalk_get_userid_by_unionid(item.uid)
205
206        work_weixin_socials = SocialAuthUser.objects.filter(provider=WORK_WEIXIN_PROVIDER, \
207                uid__contains=WORK_WEIXIN_UID_PREFIX).filter(username__in=user_email_list)
208        work_weixin_email_list = [item.username for item in work_weixin_socials]
209        work_weixin_email_uid_dict = {}
210        for item in work_weixin_socials:
211            work_weixin_email_uid_dict[item.username] = item.uid[len(WORK_WEIXIN_UID_PREFIX):]
212
213        # 4. send msg
214        site_name = get_site_name()
215
216        for email in list(set(dingtalk_email_list + work_weixin_email_list)):
217
218            should_send = []
219            for notification in user_notifications:
220                if email == notification.to_user:
221                    should_send.append(notification)
222
223            title = _("You've got %(num)s new notices on %(site_name)s:\n") % \
224                    {'num': len(should_send), 'site_name': site_name, }
225
226            has_sent = False
227
228            if not has_sent and email in dingtalk_email_list and ENABLE_DINGTALK:
229                content = '  \n  '.join([remove_html_a_element_for_dingtalk(x.format_msg()) for x in should_send])
230                self.send_dingtalk_msg(dingtalk_email_uid_dict[email], title, content)
231                has_sent = True
232
233            if not has_sent and email in work_weixin_email_list and ENABLE_WORK_WEIXIN:
234                content = ''.join([wrap_div_for_work_weixin(x.format_msg()) for x in should_send])
235                self.send_work_weixin_msg(work_weixin_email_uid_dict[email], title, content)
236                has_sent = True
237
238        translation.activate(cur_language)
239        self.log_info('reset language success')
240        return
241