mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 06:33:48 +00:00
dingtalk send msg (#4524)
* dingtalk msg * use unionid * update Co-authored-by: lian <lian@seafile.com>
This commit is contained in:
@@ -5,7 +5,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
@@ -28,9 +27,8 @@ from seahub.profile.models import Profile
|
|||||||
from seahub.avatar.models import Avatar
|
from seahub.avatar.models import Avatar
|
||||||
from seahub.group.utils import validate_group_name
|
from seahub.group.utils import validate_group_name
|
||||||
|
|
||||||
from seahub.utils import normalize_cache_key
|
from seahub.dingtalk.utils import dingtalk_get_access_token
|
||||||
from seahub.dingtalk.settings import ENABLE_DINGTALK, DINGTALK_DEPARTMENT_APP_KEY, \
|
from seahub.dingtalk.settings import ENABLE_DINGTALK, \
|
||||||
DINGTALK_DEPARTMENT_APP_SECRET, DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL, \
|
|
||||||
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, \
|
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, \
|
||||||
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, \
|
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, \
|
||||||
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL, \
|
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL, \
|
||||||
@@ -40,34 +38,6 @@ DEPARTMENT_OWNER = 'system admin'
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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):
|
def update_dingtalk_user_info(email, name, contact_email, avatar_url):
|
||||||
|
|
||||||
# make sure the contact_email is unique
|
# make sure the contact_email is unique
|
||||||
@@ -114,7 +84,7 @@ class AdminDingtalkDepartments(APIView):
|
|||||||
if not request.user.admin_permissions.can_manage_user():
|
if not request.user.admin_permissions.can_manage_user():
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
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:
|
if not access_token:
|
||||||
error_msg = '获取钉钉组织架构失败'
|
error_msg = '获取钉钉组织架构失败'
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, 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():
|
if not request.user.admin_permissions.can_manage_user():
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
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:
|
if not access_token:
|
||||||
error_msg = '获取钉钉组织架构成员失败'
|
error_msg = '获取钉钉组织架构成员失败'
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
@@ -320,7 +290,7 @@ class AdminDingtalkDepartmentsImport(APIView):
|
|||||||
error_msg = 'department_id invalid.'
|
error_msg = 'department_id invalid.'
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
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:
|
if not access_token:
|
||||||
error_msg = '获取钉钉组织架构失败'
|
error_msg = '获取钉钉组织架构失败'
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import seahub.settings as settings
|
import seahub.settings as settings
|
||||||
|
|
||||||
ENABLE_DINGTALK = getattr(settings, 'ENABLE_DINGTALK', False)
|
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
|
# for dingtalk qr connect
|
||||||
DINGTALK_QR_CONNECT_LOGIN_REMEMBER_ME = True
|
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_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_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
|
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
63
seahub/dingtalk/utils.py
Normal 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
|
@@ -0,0 +1,179 @@
|
|||||||
|
# Copyright (c) 2012-2019 Seafile Ltd.
|
||||||
|
# encoding: utf-8
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
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.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
|
||||||
|
|
||||||
|
# Get an instance of a logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# https://ding-doc.dingtalk.com/doc#/serverapi3/wvdxel
|
||||||
|
|
||||||
|
########## Utility Functions ##########
|
||||||
|
def remove_html_a_element(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)
|
||||||
|
|
||||||
|
|
||||||
|
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 notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
help = 'Send dingtalk msg to user if he/she has unseen notices every '
|
||||||
|
'period of time.'
|
||||||
|
label = "notifications_send_dingtalk_notices"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.log_debug('Start sending dingtalk msg...')
|
||||||
|
self.do_action()
|
||||||
|
self.log_debug('Finish sending dingtalk 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 do_action(self):
|
||||||
|
|
||||||
|
# check before start
|
||||||
|
access_token = dingtalk_get_access_token()
|
||||||
|
if not access_token:
|
||||||
|
self.log_error('can not get access_token')
|
||||||
|
|
||||||
|
self.dingtalk_message_send_to_conversation_url = DINGTALK_MESSAGE_SEND_TO_CONVERSATION_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 dingtalk
|
||||||
|
socials = SocialAuthUser.objects.filter(provider='dingtalk')
|
||||||
|
users = [(x.username, x.uid) 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] = dingtalk_get_userid_by_unionid(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:
|
||||||
|
user_id = user_uid_map[username]
|
||||||
|
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 = ' \n '.join([remove_html_a_element(x.format_msg()) for x in notices])
|
||||||
|
self.send_dingtalk_msg(user_id, title, content)
|
||||||
|
|
||||||
|
# reset language
|
||||||
|
translation.activate(cur_language)
|
||||||
|
self.log_info('reset language success')
|
Reference in New Issue
Block a user