diff --git a/seahub/api2/endpoints/admin/dingtalk.py b/seahub/api2/endpoints/admin/dingtalk.py
index 426b95f76f..400af0a541 100644
--- a/seahub/api2/endpoints/admin/dingtalk.py
+++ b/seahub/api2/endpoints/admin/dingtalk.py
@@ -5,7 +5,6 @@
import logging
import requests
-from django.core.cache import cache
from django.core.files.base import ContentFile
from rest_framework.authentication import SessionAuthentication
@@ -28,9 +27,8 @@ from seahub.profile.models import Profile
from seahub.avatar.models import Avatar
from seahub.group.utils import validate_group_name
-from seahub.utils import normalize_cache_key
-from seahub.dingtalk.settings import ENABLE_DINGTALK, DINGTALK_DEPARTMENT_APP_KEY, \
- DINGTALK_DEPARTMENT_APP_SECRET, DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL, \
+from seahub.dingtalk.utils import dingtalk_get_access_token
+from seahub.dingtalk.settings import ENABLE_DINGTALK, \
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL, \
@@ -40,34 +38,6 @@ DEPARTMENT_OWNER = 'system admin'
logger = logging.getLogger(__name__)
-
-def get_dingtalk_access_token():
-
- cache_key = normalize_cache_key('DINGTALK_ACCESS_TOKEN')
- access_token = cache.get(cache_key, None)
-
- if not access_token:
-
- data = {
- 'appkey': DINGTALK_DEPARTMENT_APP_KEY,
- 'appsecret': DINGTALK_DEPARTMENT_APP_SECRET,
- }
- resp_json = requests.get(DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL,
- params=data).json()
-
- access_token = resp_json.get('access_token', '')
- if not access_token:
- logger.error('failed to get dingtalk access_token')
- logger.error(data)
- logger.error(DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL)
- logger.error(resp_json)
- return ''
-
- expires_in = resp_json.get('expires_in', 7200)
- cache.set(cache_key, access_token, expires_in)
-
- return access_token
-
def update_dingtalk_user_info(email, name, contact_email, avatar_url):
# make sure the contact_email is unique
@@ -114,7 +84,7 @@ class AdminDingtalkDepartments(APIView):
if not request.user.admin_permissions.can_manage_user():
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
- access_token = get_dingtalk_access_token()
+ access_token = dingtalk_get_access_token()
if not access_token:
error_msg = '获取钉钉组织架构失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
@@ -151,7 +121,7 @@ class AdminDingtalkDepartmentMembers(APIView):
if not request.user.admin_permissions.can_manage_user():
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
- access_token = get_dingtalk_access_token()
+ access_token = dingtalk_get_access_token()
if not access_token:
error_msg = '获取钉钉组织架构成员失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
@@ -320,7 +290,7 @@ class AdminDingtalkDepartmentsImport(APIView):
error_msg = 'department_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
- access_token = get_dingtalk_access_token()
+ access_token = dingtalk_get_access_token()
if not access_token:
error_msg = '获取钉钉组织架构失败'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
diff --git a/seahub/dingtalk/settings.py b/seahub/dingtalk/settings.py
index ca3756702b..3d806defce 100644
--- a/seahub/dingtalk/settings.py
+++ b/seahub/dingtalk/settings.py
@@ -1,6 +1,8 @@
import seahub.settings as settings
ENABLE_DINGTALK = getattr(settings, 'ENABLE_DINGTALK', False)
+DINGTALK_AGENT_ID = getattr(settings, 'DINGTALK_AGENT_ID', '')
+DINGTALK_GET_USERID_BY_UNIONID = getattr(settings, 'DINGTALK_GET_USERID_BY_UNIONID', 'https://oapi.dingtalk.com/user/getUseridByUnionid')
# for dingtalk qr connect
DINGTALK_QR_CONNECT_LOGIN_REMEMBER_ME = True
@@ -21,3 +23,6 @@ DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL = getattr(settings, 'DINGTALK_DEPARTMENT
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL = getattr(settings, 'DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL', 'https://oapi.dingtalk.com/department/get')
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL = getattr(settings, 'DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL', 'https://oapi.dingtalk.com/user/listbypage')
DINGTALK_DEPARTMENT_USER_SIZE = 100
+
+# for dingtalk message
+DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL = getattr(settings, 'DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL', 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2')
diff --git a/seahub/dingtalk/utils.py b/seahub/dingtalk/utils.py
new file mode 100644
index 0000000000..d803f8cb3f
--- /dev/null
+++ b/seahub/dingtalk/utils.py
@@ -0,0 +1,63 @@
+import logging
+import requests
+
+from django.core.cache import cache
+from seahub.utils import normalize_cache_key
+
+from seahub.dingtalk.settings import DINGTALK_DEPARTMENT_APP_KEY, \
+ DINGTALK_DEPARTMENT_APP_SECRET, \
+ DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL, \
+ DINGTALK_GET_USERID_BY_UNIONID
+
+logger = logging.getLogger(__name__)
+
+def dingtalk_get_access_token():
+
+ cache_key = normalize_cache_key('DINGTALK_ACCESS_TOKEN')
+ access_token = cache.get(cache_key, None)
+ if access_token:
+ return access_token
+
+ data = {
+ 'appkey': DINGTALK_DEPARTMENT_APP_KEY,
+ 'appsecret': DINGTALK_DEPARTMENT_APP_SECRET,
+ }
+ resp_json = requests.get(DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL,
+ params=data).json()
+
+ access_token = resp_json.get('access_token', '')
+ if not access_token:
+ logger.error('failed to get dingtalk access_token')
+ logger.error(DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL)
+ logger.error(data)
+ logger.error(resp_json)
+ return ''
+
+ expires_in = resp_json.get('expires_in', 7200)
+ cache.set(cache_key, access_token, expires_in)
+
+ return access_token
+
+def dingtalk_get_userid_by_unionid(union_id):
+
+ cache_key = normalize_cache_key('DINGTALK_UNION_ID_%s' % union_id)
+ user_id = cache.get(cache_key, None)
+ if user_id:
+ return user_id
+
+ access_token = dingtalk_get_access_token()
+ data = {
+ 'access_token': access_token,
+ 'unionid': union_id,
+ }
+ resp_json = requests.get(DINGTALK_GET_USERID_BY_UNIONID, params=data).json()
+ user_id = resp_json.get('userid', '')
+ if not user_id:
+ logger.error('failed to get userid by unionid: %s' % union_id)
+ logger.error(DINGTALK_GET_USERID_BY_UNIONID)
+ logger.error(data)
+ logger.error(resp_json)
+ return ''
+
+ cache.set(cache_key, user_id)
+ return user_id
diff --git a/seahub/notifications/management/commands/send_notices_to_social_account.py b/seahub/notifications/management/commands/send_notices_to_social_account.py
new file mode 100644
index 0000000000..60d84a4000
--- /dev/null
+++ b/seahub/notifications/management/commands/send_notices_to_social_account.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2012-2019 Seafile Ltd.
+# encoding: utf-8
+from datetime import datetime
+import logging
+import re
+import json
+import requests
+
+from django.core.management.base import BaseCommand
+from django.core.urlresolvers import reverse
+from django.utils import translation
+from django.utils.translation import ugettext as _
+
+from seahub.base.models import CommandsLastCheck
+from seahub.notifications.models import UserNotification
+from seahub.utils import get_site_scheme_and_netloc, get_site_name
+from seahub.auth.models import SocialAuthUser
+
+from seahub.dingtalk.utils import dingtalk_get_access_token, dingtalk_get_userid_by_unionid
+from seahub.dingtalk.settings import DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL, \
+ DINGTALK_AGENT_ID, ENABLE_DINGTALK
+
+from seahub.work_weixin.utils import get_work_weixin_access_token, handler_work_weixin_api_response
+from seahub.work_weixin.settings import WORK_WEIXIN_NOTIFICATIONS_URL, \
+ WORK_WEIXIN_PROVIDER, WORK_WEIXIN_UID_PREFIX, WORK_WEIXIN_AGENT_ID, ENABLE_WORK_WEIXIN
+
+# Get an instance of a logger
+logger = logging.getLogger(__name__)
+
+########## Utility Functions ##########
+
+# https://ding-doc.dingtalk.com/doc#/serverapi3/wvdxel
+def remove_html_a_element_for_dingtalk(s):
+ """
+ Replace xx to xx and wrap content with
.
+ """
+ patt = '(.+?)'
+
+ def repl(matchobj):
+ return matchobj.group(1)
+
+ return re.sub(patt, repl, s)
+
+# https://work.weixin.qq.com/api/doc#90000/90135/90236/
+def wrap_div_for_work_weixin(s):
+ """
+ Replace xx to xx and wrap content with .
+ """
+ patt = '(.+?)'
+
+ def repl(matchobj):
+ return matchobj.group(1)
+
+ return '' + re.sub(patt, repl, s) + '
'
+
+class CommandLogMixin(object):
+
+ def println(self, msg):
+ self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
+
+ def log_error(self, msg):
+ logger.error(msg)
+ self.println(msg)
+
+ def log_info(self, msg):
+ logger.info(msg)
+ self.println(msg)
+
+ def log_debug(self, msg):
+ logger.debug(msg)
+ self.println(msg)
+
+
+class Command(BaseCommand, CommandLogMixin):
+ """ send dingtalk/work weixin notifications
+ """
+
+ help = "Send notices to user's social account if he/she has unseen notices every period of time."
+ label = "notifications_send_notices_to_social_account"
+
+ def handle(self, *args, **options):
+
+ if ENABLE_DINGTALK:
+ self.log_debug('Start sending dingtalk msg...')
+ if ENABLE_WORK_WEIXIN:
+ self.log_debug('Start sending work weixin msg...')
+
+ self.do_action()
+
+ if ENABLE_DINGTALK:
+ self.log_debug('Finish sending dingtalk msg.\n')
+ if ENABLE_WORK_WEIXIN:
+ self.log_debug('Finish sending work weixin msg.\n')
+
+ def send_dingtalk_msg(self, user_id, title, content):
+
+ self.log_info('Send dingtalk msg to user: %s, msg: %s' % (user_id, content))
+ data = {
+ "agent_id": DINGTALK_AGENT_ID,
+ "userid_list": user_id,
+ "msg": {
+ "msgtype": "markdown",
+ "markdown": {
+ "title": title,
+ "text": content
+ }
+ }
+ }
+ resp_json = requests.post(self.dingtalk_message_send_to_conversation_url,
+ data=json.dumps(data)).json()
+ if resp_json.get('errcode') != 0:
+ self.log_info(resp_json)
+
+ def send_work_weixin_msg(self, uid, title, content):
+
+ self.log_info('Send wechat msg to user: %s, msg: %s' % (uid, content))
+
+ data = {
+ "touser": uid,
+ "agentid": WORK_WEIXIN_AGENT_ID,
+ 'msgtype': 'textcard',
+ 'textcard': {
+ 'title': title,
+ 'description': content,
+ 'url': self.detail_url,
+ },
+ }
+
+ api_response = requests.post(self.work_weixin_notifications_url, json=data)
+ api_response_dic = handler_work_weixin_api_response(api_response)
+ if api_response_dic:
+ self.log_info(api_response_dic)
+ else:
+ self.log_error('can not get work weixin notifications API response')
+
+ def do_action(self):
+
+ if not ENABLE_DINGTALK and not ENABLE_WORK_WEIXIN:
+ self.log_info('No social account enabled')
+ return
+
+ dingtalk_access_token = ''
+ work_weixin_access_token = ''
+
+ if ENABLE_DINGTALK:
+
+ dingtalk_access_token = dingtalk_get_access_token()
+ if not dingtalk_access_token:
+ self.log_error('can not get access token for dingtalk')
+ else:
+ self.dingtalk_message_send_to_conversation_url = DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL + \
+ '?access_token=' + dingtalk_access_token
+ self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
+
+ if ENABLE_WORK_WEIXIN:
+
+ work_weixin_access_token = get_work_weixin_access_token()
+ if not work_weixin_access_token:
+ self.log_error('can not get access token for work weixin')
+ else:
+ self.work_weixin_notifications_url = WORK_WEIXIN_NOTIFICATIONS_URL + \
+ '?access_token=' + work_weixin_access_token
+ self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
+
+ if not dingtalk_access_token and not work_weixin_access_token:
+ return
+
+ # save current language
+ cur_language = translation.get_language()
+ # active zh-cn
+ translation.activate('zh-cn')
+ self.log_info('the language is set to zh-cn')
+
+ # 1. get previous time that command last runs
+ now = datetime.now()
+ today = datetime.now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)
+
+ try:
+ cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
+ self.log_debug('Last check time is %s' % cmd_last_check.last_check)
+
+ last_check_dt = cmd_last_check.last_check
+
+ cmd_last_check.last_check = now
+ cmd_last_check.save()
+ except CommandsLastCheck.DoesNotExist:
+ last_check_dt = today
+ self.log_debug('Create new last check time: %s' % now)
+ CommandsLastCheck(command_type=self.label, last_check=now).save()
+
+ # 2. get all unseen notices
+ user_notifications = UserNotification.objects.filter(timestamp__gt=last_check_dt).filter(seen=False)
+ self.log_info('Found %d notices' % user_notifications.count())
+ if user_notifications.count() == 0:
+ return
+
+ # 3. get all users should send notice to
+ user_email_list = list(set([item.to_user for item in user_notifications]))
+
+ dingtail_socials = SocialAuthUser.objects.filter(provider='dingtalk').filter(username__in=user_email_list)
+ dingtalk_email_list = [item.username for item in dingtail_socials]
+ dingtalk_email_uid_dict = {}
+ for item in dingtail_socials:
+ dingtalk_email_uid_dict[item.username] = dingtalk_get_userid_by_unionid(item.uid)
+
+ work_weixin_socials = SocialAuthUser.objects.filter(provider=WORK_WEIXIN_PROVIDER, \
+ uid__contains=WORK_WEIXIN_UID_PREFIX).filter(username__in=user_email_list)
+ work_weixin_email_list = [item.username for item in work_weixin_socials]
+ work_weixin_email_uid_dict = {}
+ for item in work_weixin_socials:
+ work_weixin_email_uid_dict[item.username] = item.uid[len(WORK_WEIXIN_UID_PREFIX):]
+
+ # 4. send msg
+ site_name = get_site_name()
+
+ for email in list(set(dingtalk_email_list + work_weixin_email_list)):
+
+ should_send = []
+ for notification in user_notifications:
+ if email == notification.to_user:
+ should_send.append(notification)
+
+ title = _("You've got %(num)s new notices on %(site_name)s:\n") % \
+ {'num': len(should_send), 'site_name': site_name, }
+
+ has_sent = False
+
+ if not has_sent and email in dingtalk_email_list and ENABLE_DINGTALK:
+ content = ' \n '.join([remove_html_a_element_for_dingtalk(x.format_msg()) for x in should_send])
+ self.send_dingtalk_msg(dingtalk_email_uid_dict[email], title, content)
+ has_sent = True
+
+ if not has_sent and email in work_weixin_email_list and ENABLE_WORK_WEIXIN:
+ content = ''.join([wrap_div_for_work_weixin(x.format_msg()) for x in should_send])
+ self.send_work_weixin_msg(work_weixin_email_uid_dict[email], title, content)
+ has_sent = True
+
+ translation.activate(cur_language)
+ self.log_info('reset language success')
+ return
diff --git a/seahub/notifications/management/commands/send_work_weixin_notifications.py b/seahub/notifications/management/commands/send_work_weixin_notifications.py
deleted file mode 100644
index aca6bebb38..0000000000
--- a/seahub/notifications/management/commands/send_work_weixin_notifications.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright (c) 2012-2019 Seafile Ltd.
-# encoding: utf-8
-from datetime import datetime
-import logging
-import re
-import requests
-
-from django.core.management.base import BaseCommand
-from django.core.urlresolvers import reverse
-from django.utils import translation
-from django.utils.translation import ungettext
-
-from seahub.base.models import CommandsLastCheck
-from seahub.notifications.models import UserNotification
-from seahub.utils import get_site_scheme_and_netloc, get_site_name
-from seahub.auth.models import SocialAuthUser
-from seahub.work_weixin.utils import work_weixin_notifications_check, \
- get_work_weixin_access_token, handler_work_weixin_api_response
-from seahub.work_weixin.settings import WORK_WEIXIN_NOTIFICATIONS_URL, \
- WORK_WEIXIN_PROVIDER, WORK_WEIXIN_UID_PREFIX, WORK_WEIXIN_AGENT_ID
-
-# Get an instance of a logger
-logger = logging.getLogger(__name__)
-
-
-# https://work.weixin.qq.com/api/doc#90000/90135/90236/
-
-########## Utility Functions ##########
-def wrap_div(s):
- """
- Replace xx to xx and wrap content with .
- """
- patt = '(.+?)'
-
- def repl(matchobj):
- return matchobj.group(1)
-
- return '' + re.sub(patt, repl, s) + '
'
-
-
-class CommandLogMixin(object):
- def println(self, msg):
- self.stdout.write('[%s] %s\n' % (str(datetime.now()), msg))
-
- def log_error(self, msg):
- logger.error(msg)
- self.println(msg)
-
- def log_info(self, msg):
- logger.info(msg)
- self.println(msg)
-
- def log_debug(self, msg):
- logger.debug(msg)
- self.println(msg)
-
-
-#######################################
-
-class Command(BaseCommand, CommandLogMixin):
- """ send work weixin notifications
- """
-
- help = 'Send WeChat Work msg to user if he/she has unseen notices every '
- 'period of time.'
- label = "notifications_send_wxwork_notices"
-
- def handle(self, *args, **options):
- self.log_debug('Start sending work weixin msg...')
- self.do_action()
- self.log_debug('Finish sending work weixin msg.\n')
-
- def send_work_weixin_msg(self, uid, title, content):
-
- self.log_info('Send wechat msg to user: %s, msg: %s' % (uid, content))
-
- data = {
- "touser": uid,
- "agentid": WORK_WEIXIN_AGENT_ID,
- 'msgtype': 'textcard',
- 'textcard': {
- 'title': title,
- 'description': content,
- 'url': self.detail_url,
- },
- }
-
- api_response = requests.post(self.work_weixin_notifications_url, json=data)
- api_response_dic = handler_work_weixin_api_response(api_response)
- if api_response_dic:
- self.log_info(api_response_dic)
- else:
- self.log_error('can not get work weixin notifications API response')
-
- def do_action(self):
- # check before start
- if not work_weixin_notifications_check():
- self.log_error('work weixin notifications settings check failed')
- return
-
- access_token = get_work_weixin_access_token()
- if not access_token:
- self.log_error('can not get access_token')
-
- self.work_weixin_notifications_url = WORK_WEIXIN_NOTIFICATIONS_URL + '?access_token=' + access_token
- self.detail_url = get_site_scheme_and_netloc().rstrip('/') + reverse('user_notification_list')
- site_name = get_site_name()
-
- # start
- now = datetime.now()
- today = datetime.now().replace(hour=0).replace(minute=0).replace(
- second=0).replace(microsecond=0)
-
- # 1. get all users who are connected work weixin
- socials = SocialAuthUser.objects.filter(provider=WORK_WEIXIN_PROVIDER, uid__contains=WORK_WEIXIN_UID_PREFIX)
- users = [(x.username, x.uid[len(WORK_WEIXIN_UID_PREFIX):]) for x in socials]
- self.log_info('Found %d users' % len(users))
- if not users:
- return
-
- user_uid_map = {}
- for username, uid in users:
- user_uid_map[username] = uid
-
- # 2. get previous time that command last runs
- try:
- cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
- self.log_debug('Last check time is %s' % cmd_last_check.last_check)
-
- last_check_dt = cmd_last_check.last_check
-
- cmd_last_check.last_check = now
- cmd_last_check.save()
- except CommandsLastCheck.DoesNotExist:
- last_check_dt = today
- self.log_debug('Create new last check time: %s' % now)
- CommandsLastCheck(command_type=self.label, last_check=now).save()
-
- # 3. get all unseen notices for those users
- qs = UserNotification.objects.filter(
- timestamp__gt=last_check_dt
- ).filter(seen=False).filter(
- to_user__in=list(user_uid_map.keys())
- )
- self.log_info('Found %d notices' % qs.count())
- if qs.count() == 0:
- return
-
- user_notices = {}
- for q in qs:
- if q.to_user not in user_notices:
- user_notices[q.to_user] = [q]
- else:
- user_notices[q.to_user].append(q)
-
- # save current language
- cur_language = translation.get_language()
- # active zh-cn
- translation.activate('zh-cn')
- self.log_info('the language is set to zh-cn')
-
- # 4. send msg to users
- for username, uid in users:
- notices = user_notices.get(username, [])
- count = len(notices)
- if count == 0:
- continue
-
- title = ungettext(
- "\n"
- "You've got 1 new notice on %(site_name)s:\n",
- "\n"
- "You've got %(num)s new notices on %(site_name)s:\n",
- count
- ) % {'num': count, 'site_name': site_name, }
-
- content = ''.join([wrap_div(x.format_msg()) for x in notices])
- self.send_work_weixin_msg(uid, title, content)
-
- # reset language
- translation.activate(cur_language)
- self.log_info('reset language success')