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