1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-25 14:50:29 +00:00

Send notices to social account (#4606)

* dingtalk msg commit for master branch

* send_notices_to_social_account

Co-authored-by: lian <lian@seafile.com>
This commit is contained in:
lian
2020-07-10 16:00:52 +08:00
committed by GitHub
parent bf3b8d5d23
commit 7f6f29ccfe
5 changed files with 313 additions and 217 deletions

View File

@@ -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)

View File

@@ -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')

63
seahub/dingtalk/utils.py Normal file
View File

@@ -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

View File

@@ -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 <a ..>xx</a> to xx and wrap content with <div></div>.
"""
patt = '<a.*?>(.+?)</a>'
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 <a ..>xx</a> to xx and wrap content with <div></div>.
"""
patt = '<a.*?>(.+?)</a>'
def repl(matchobj):
return matchobj.group(1)
return '<div class="highlight">' + re.sub(patt, repl, s) + '</div>'
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

View File

@@ -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 <a ..>xx</a> to xx and wrap content with <div></div>.
"""
patt = '<a.*?>(.+?)</a>'
def repl(matchobj):
return matchobj.group(1)
return '<div class="highlight">' + re.sub(patt, repl, s) + '</div>'
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')