优化工单模块,增加企微模块
This commit is contained in:
9
sg_wechat_enterprise/models/__init__.py
Normal file
9
sg_wechat_enterprise/models/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from . import we_app
|
||||
from . import we_conf
|
||||
from . import we_send_message
|
||||
from . import we_receive_message
|
||||
from . import we_receive_message_process
|
||||
from . import res_users
|
||||
from . import we_tools
|
||||
from . import mail
|
||||
from . import client
|
||||
11
sg_wechat_enterprise/models/client/__init__.py
Normal file
11
sg_wechat_enterprise/models/client/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from wechatpy.enterprise import WeChatClient
|
||||
|
||||
from . import api
|
||||
|
||||
|
||||
class JkmWeChatClient(WeChatClient):
|
||||
def __init__(self, corp_id, secret, session=None):
|
||||
super(JkmWeChatClient, self).__init__(corp_id, secret, session=session)
|
||||
|
||||
oauth = api.JkmWechatOauth()
|
||||
user = api.JkmWechatUser()
|
||||
2
sg_wechat_enterprise/models/client/api/__init__.py
Normal file
2
sg_wechat_enterprise/models/client/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .jkm_user import JkmWechatUser
|
||||
from .jkm_oauth import JkmWechatOauth
|
||||
53
sg_wechat_enterprise/models/client/api/jkm_oauth.py
Normal file
53
sg_wechat_enterprise/models/client/api/jkm_oauth.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import six
|
||||
from wechatpy.client.api.base import BaseWeChatAPI
|
||||
|
||||
|
||||
class JkmWechatOauth(BaseWeChatAPI):
|
||||
OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize'
|
||||
|
||||
def authorize_url(self, redirect_uri, state=None, agent_id=None, scope='snsapi_base'):
|
||||
"""
|
||||
构造网页授权链接
|
||||
详情请参考
|
||||
https://work.weixin.qq.com/api/doc#90000/90135/91022
|
||||
|
||||
:param redirect_uri: 授权后重定向的回调链接地址
|
||||
:param state: 重定向后会带上 state 参数
|
||||
:param agent_id: 企业应用的id
|
||||
:param scope: 应用授权作用域
|
||||
:return: 返回的 JSON 数据包
|
||||
"""
|
||||
redirect_uri = six.moves.urllib.parse.quote(redirect_uri, safe=b'')
|
||||
url_list = [
|
||||
self.OAUTH_BASE_URL,
|
||||
'?appid=',
|
||||
self._client.corp_id,
|
||||
'&redirect_uri=',
|
||||
redirect_uri,
|
||||
'&response_type=code&scope=',
|
||||
scope,
|
||||
]
|
||||
if state:
|
||||
url_list.extend(['&state=', state])
|
||||
url_list.append('#wechat_redirect')
|
||||
if agent_id:
|
||||
url_list.extend(['&agentid=', str(agent_id)])
|
||||
return ''.join(url_list)
|
||||
|
||||
def get_user_info(self, code):
|
||||
"""
|
||||
获取访问用户身份
|
||||
详情请参考
|
||||
https://work.weixin.qq.com/api/doc#90000/90135/91023
|
||||
|
||||
:param code: 通过成员授权获取到的code
|
||||
:return: 返回的 JSON 数据包
|
||||
"""
|
||||
|
||||
return self._get(
|
||||
'user/getuserinfo',
|
||||
params={
|
||||
'code': code,
|
||||
}
|
||||
)
|
||||
21
sg_wechat_enterprise/models/client/api/jkm_user.py
Normal file
21
sg_wechat_enterprise/models/client/api/jkm_user.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from wechatpy.enterprise.client.api import WeChatUser
|
||||
|
||||
|
||||
class JkmWechatUser(WeChatUser):
|
||||
|
||||
def get_detail(self, user_ticket):
|
||||
"""
|
||||
获取访问用户敏感信息
|
||||
详情请参考
|
||||
https://developer.work.weixin.qq.com/document/path/95833
|
||||
|
||||
:param user_ticket: 成员票据
|
||||
:return: 返回的 JSON 数据包
|
||||
"""
|
||||
return self._post(
|
||||
'user/getuserdetail',
|
||||
data={
|
||||
'user_ticket': user_ticket
|
||||
}
|
||||
)
|
||||
|
||||
121
sg_wechat_enterprise/models/mail.py
Normal file
121
sg_wechat_enterprise/models/mail.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# -*- coding: utf-8-*-
|
||||
import re
|
||||
##过滤HTML中的标签
|
||||
#将HTML中标签等信息去掉
|
||||
#@param htmlstr HTML字符串.
|
||||
def filter_tags(htmlstr):
|
||||
#先过滤CDATA
|
||||
re_cdata=re.compile('//<!\[CDATA\[[^>]*//\]\]>',re.I) #匹配CDATA
|
||||
re_script=re.compile('<\s*script[^>]*>[^<]*<\s*/\s*script\s*>',re.I)#Script
|
||||
re_style=re.compile('<\s*style[^>]*>[^<]*<\s*/\s*style\s*>',re.I)#style
|
||||
re_br=re.compile('<br\s*?/?>')#处理换行
|
||||
re_h=re.compile('</?\w+[^>]*>')#HTML标签
|
||||
re_comment=re.compile('<!--[^>]*-->')#HTML注释
|
||||
s=re_cdata.sub('',htmlstr)#去掉CDATA
|
||||
s=re_script.sub('',s) #去掉SCRIPT
|
||||
s=re_style.sub('',s)#去掉style
|
||||
s=re_br.sub('\n',s)#将br转换为换行
|
||||
s=re_h.sub('',s) #去掉HTML 标签
|
||||
s=re_comment.sub('',s)#去掉HTML注释
|
||||
#去掉多余的空行
|
||||
blank_line=re.compile('\n+')
|
||||
s=blank_line.sub('\n',s)
|
||||
s=replaceCharEntity(s)#替换实体
|
||||
return s
|
||||
|
||||
##替换常用HTML字符实体.
|
||||
#使用正常的字符替换HTML中特殊的字符实体.
|
||||
#你可以添加新的实体字符到CHAR_ENTITIES中,处理更多HTML字符实体.
|
||||
#@param htmlstr HTML字符串.
|
||||
def replaceCharEntity(htmlstr):
|
||||
CHAR_ENTITIES={'nbsp':' ','160':' ',
|
||||
'lt':'<','60':'<',
|
||||
'gt':'>','62':'>',
|
||||
'amp':'&','38':'&',
|
||||
'quot':'"','34':'"',}
|
||||
|
||||
re_charEntity=re.compile(r'&#?(?P<name>\w+);')
|
||||
sz=re_charEntity.search(htmlstr)
|
||||
while sz:
|
||||
entity=sz.group()#entity全称,如>
|
||||
key=sz.group('name')#去除&;后entity,如>为gt
|
||||
try:
|
||||
htmlstr=re_charEntity.sub(CHAR_ENTITIES[key],htmlstr,1)
|
||||
sz=re_charEntity.search(htmlstr)
|
||||
except KeyError:
|
||||
#以空串代替
|
||||
htmlstr=re_charEntity.sub('',htmlstr,1)
|
||||
sz=re_charEntity.search(htmlstr)
|
||||
return htmlstr
|
||||
|
||||
def repalce(s,re_exp,repl_string):
|
||||
return re_exp.sub(repl_string,s)
|
||||
|
||||
|
||||
class MailMessage(models.Model):
|
||||
_inherit = 'mail.message'
|
||||
|
||||
we_is_send = fields.Selection([('-1', '不需要发送'), ('0', '需要发送'), ('1', '已发送')], string='订订消息发送状态', default='0',
|
||||
index=True)
|
||||
we_error_msg = fields.Char('消息不发送原因')
|
||||
|
||||
def send_we_message(self):
|
||||
"""
|
||||
定时批量发送钉钉消息
|
||||
:return:
|
||||
"""
|
||||
messages = self.search([('we_is_send', '=', '0')])
|
||||
if messages and len(messages) > 0:
|
||||
try:
|
||||
messages.send_message_to_we()
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
def send_message_to_we(self):
|
||||
"""
|
||||
:param agent_id: 必填,企业应用的id,整型。可在应用的设置页面查看。
|
||||
:param user_ids: 成员ID列表(消息接收者,多个接收者用‘|’分隔,最多支持1000个)。
|
||||
:param title: 标题,不超过128个字节,超过会自动截断。
|
||||
:param description: 必填,描述,不超过512个字节,超过会自动截断
|
||||
:param url: 必填,点击后跳转的链接。
|
||||
:param btntxt: 按钮文字。 默认为“详情”, 不超过4个文字,超过自动截断。
|
||||
:param party_ids: 部门ID列表。
|
||||
:param tag_ids: 标签ID列表。
|
||||
:return:
|
||||
"""
|
||||
# 获取配置信息
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
# 获取wechat
|
||||
wechat,agent_id = self.env['we.config'].sudo().get_odoo_wechat()
|
||||
for m in self:
|
||||
we_user = m.get_we_user()
|
||||
if we_user and len(we_user)>0:
|
||||
try:
|
||||
_logger.info('wechat:%s' % wechat)
|
||||
model = self.env['ir.model'].search([('model', '=', m.model)], limit=1)
|
||||
title = "[%s] %s" % (model.name, m.record_name or m.subject or '')
|
||||
description = filter_tags(m.body) if len(filter_tags(m.body)) > 0 else '有新的状态和数据变更,请注意跟进.'
|
||||
url = '{base_url}/web#id={id}&model={model}&view_type=form'.format(
|
||||
base_url=config.get_param('web.base.url'),
|
||||
id=m.res_id,
|
||||
model=m.model)
|
||||
_logger.info('description:%s title:%s' % (description, title))
|
||||
ret = wechat.message.send_text_card(agent_id, we_user , title, description, url, btntxt='点此跟进..',
|
||||
party_ids='', tag_ids='')
|
||||
_logger.info('message_id:%s ret:%s' % (m.message_id, ret))
|
||||
m.write({'we_is_send':'1','we_error_msg':''})
|
||||
except Exception as ex:
|
||||
m.we_error_msg = '向企业微信发送消息失败! 消息编号:%s 原因:%s' % (m.message_id, ex)
|
||||
_logger.error('向企业微信发送消息失败! 消息编号:%s 原因:%s' % (m.message_id, ex))
|
||||
else:
|
||||
m.write({'we_is_send': '-1', 'we_error_msg': '无法发送,没有收件人!'})
|
||||
_logger.info('message_id:%s 不需要发送,没有收件人!' % m.message_id)
|
||||
|
||||
|
||||
return False
|
||||
26
sg_wechat_enterprise/models/res_users.py
Normal file
26
sg_wechat_enterprise/models/res_users.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
from odoo import models, fields, api
|
||||
from odoo.http import request
|
||||
from odoo.exceptions import AccessDenied
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
we_employee_id = fields.Char(string=u'企业微信账号', default="")
|
||||
|
||||
def _check_credentials(self, we_employee_id, env):
|
||||
"""
|
||||
用户验证
|
||||
"""
|
||||
try:
|
||||
return super(ResUsers, self)._check_credentials(we_employee_id, env)
|
||||
except AccessDenied:
|
||||
user_id = self.env['res.users'].sudo().search([('we_employee_id', '=', we_employee_id)])
|
||||
if not (user_id and user_id.id == self.env.uid):
|
||||
raise
|
||||
213
sg_wechat_enterprise/models/we_app.py
Normal file
213
sg_wechat_enterprise/models/we_app.py
Normal file
@@ -0,0 +1,213 @@
|
||||
from odoo import api, models, exceptions, fields
|
||||
import logging
|
||||
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseApp(models.Model):
|
||||
_name = 'we.app'
|
||||
_description = 'Wechat Enterprise App Manage'
|
||||
|
||||
agentid = fields.Integer(string=u'应用ID', required=True)
|
||||
code = fields.Char(string=u'自定义代码')
|
||||
name = fields.Char(u'企业号应用名称')
|
||||
description = fields.Text(u'企业号应用详情')
|
||||
redirect_domain = fields.Char(u'企业应用可信域名')
|
||||
isreportuser = fields.Selection([('0', u'不接受'), ('1', u'接收')], u'变更通知', required=True, default='0')
|
||||
isreportenter = fields.Selection([('0', u'不接受'),
|
||||
('1', u'接收')], u'进入应用事件上报', required=True, default='0')
|
||||
|
||||
enterprise_id = fields.Many2one('we.config', string=u'企业微信')
|
||||
|
||||
secret = fields.Char(string=u'Secret', size=100)
|
||||
home_url = fields.Char(u'主页型应用url')
|
||||
square_logo_url = fields.Char(u'方形头像url')
|
||||
round_logo_url = fields.Char(u'圆形头像url')
|
||||
type = fields.Selection([('1', u'消息型'), ('2', u'主页型')], u'应用类型', required=True, default='1')
|
||||
allow_userinfos = fields.Char(u'企业应用可见范围(人员)')
|
||||
allow_partys = fields.Char(u'企业应用可见范围(部门)')
|
||||
allow_tags = fields.Char(u'企业应用可见范围(标签)')
|
||||
report_location_flag = fields.Selection([('0', u'不上报'), ('1', u'进入会话上报'), ('2', u'持续上报')], u'位置上报',
|
||||
required=True, default='1')
|
||||
logo_mediaid = fields.Char(u'企业应用头像的mediaid')
|
||||
close = fields.Selection([('0', u'否'), ('1', u'是')], u'是否禁用', required=True, default='0')
|
||||
app_menu_ids = fields.One2many('we.app.menu', 'agentid', string=u'自定义菜单')
|
||||
Token = fields.Char(u'Token')
|
||||
EncodingAESKey = fields.Char(u'EncodingAESKey')
|
||||
|
||||
def pull_app_from_we(self, wechat):
|
||||
"""
|
||||
从微信获取app列表
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
app_lists = wechat.agent.list()
|
||||
if app_lists:
|
||||
for app_list in app_lists:
|
||||
if 'agentid' in app_list:
|
||||
app_detail = wechat.agent.get(app_list['agentid'])
|
||||
if app_detail:
|
||||
data = {
|
||||
'agentid': str(app_detail['agentid'])
|
||||
}
|
||||
my_app = request.env["we.app"].search(
|
||||
[("agentid", "=", str(app_detail['agentid']))])
|
||||
if my_app and len(my_app) > 0:
|
||||
continue
|
||||
data['name'] = app_detail['name']
|
||||
data['square_logo_url'] = app_detail['square_logo_url']
|
||||
data['description'] = app_detail['description']
|
||||
data['close'] = str(app_detail['close'])
|
||||
data['redirect_domain'] = app_detail['redirect_domain']
|
||||
data['report_location_flag'] = str(app_detail['report_location_flag'])
|
||||
data['isreportenter'] = str(app_detail['isreportenter'])
|
||||
data['enterprise_id'] = self.id
|
||||
request.env["we.app"].create(data)
|
||||
|
||||
except Exception as e:
|
||||
raise Warning((u'获取应用列表失败,原因:%s', str(e)))
|
||||
|
||||
def push_app_to_we(self):
|
||||
"""
|
||||
同步app到微信
|
||||
:return:
|
||||
"""
|
||||
wechat = self.env['we.config'].sudo().get_wechat()
|
||||
if wechat:
|
||||
try:
|
||||
for account in self:
|
||||
app_json = {
|
||||
'name': account.name
|
||||
}
|
||||
if account.description:
|
||||
app_json['description'] = account.description
|
||||
if account.redirect_domain:
|
||||
app_json['redirect_domain'] = account.redirect_domain
|
||||
app_json['agentid'] = int(account.agentid)
|
||||
app_json['report_location_flag'] = int(account.report_location_flag)
|
||||
if account.type == "1": # 消息型应用
|
||||
if account.name and account.agentid \
|
||||
and account.isreportuser and account.isreportenter and account.report_location_flag:
|
||||
app_json['isreportuser'] = int(account.isreportuser)
|
||||
app_json['isreportenter'] = int(account.isreportenter)
|
||||
wechat.agent.set(agent_id=app_json['agentid'], name=app_json['name'],
|
||||
description=app_json['description'],
|
||||
redirect_domain=app_json['redirect_domain'],
|
||||
is_report_user=app_json['isreportuser'],
|
||||
is_report_enter=app_json['isreportenter'],
|
||||
report_location_flag=app_json['report_location_flag'])
|
||||
elif account.type == "2": # 主页型应用
|
||||
if account.name and account.agentid \
|
||||
and account.report_location_flag and account.home_url:
|
||||
app_json['home_url'] = account.home_url
|
||||
wechat.agent.set(agent_id=app_json['agentid'], name=app_json['name'],
|
||||
description=app_json['description'],
|
||||
redirect_domain=app_json['redirect_domain'],
|
||||
is_report_user=app_json['isreportuser'],
|
||||
is_report_enter=app_json['isreportenter'],
|
||||
report_location_flag=app_json['report_location_flag'])
|
||||
|
||||
except Exception as e:
|
||||
_logger.warning(u'更新app失败,原因:%s', str(e))
|
||||
raise Warning(u'更新app失败,原因:%s', str(e))
|
||||
else:
|
||||
raise exceptions.Warning(u"初始化企业号失败")
|
||||
|
||||
def update_app_menu(self):
|
||||
"""
|
||||
同步菜单至app
|
||||
:return:
|
||||
"""
|
||||
wechat = self.env['we.config'].sudo().get_wechat(agentID=self.agentid)
|
||||
menus = self.env['we.app.menu'].sudo().search([("agentid", "=", self.name)])
|
||||
wechat.menu.delete(agent_id=self.agentid)
|
||||
menu_json = {'button': []}
|
||||
button = []
|
||||
if wechat and menus:
|
||||
for menu in menus:
|
||||
menu_data = {
|
||||
'name': menu['name']
|
||||
}
|
||||
if not menu['partner_menu_id']:
|
||||
sub_menus = request.env['we.app.menu'].sudo().search(
|
||||
[("agentid", "=", self.name), ("partner_menu_id", "=", menu['name'])])
|
||||
if sub_menus and (len(sub_menus) > 0) and (len(sub_menus) < 6):
|
||||
sub_menu_list = []
|
||||
for sub_menu in sub_menus:
|
||||
sub_menu_data = {
|
||||
'name': sub_menu['name']
|
||||
}
|
||||
if menu['type'] == 'xml' or menu['type'] == 'sub_button':
|
||||
sub_menu_data['type'] = sub_menu['type']
|
||||
sub_menu_data['url'] = sub_menu['url']
|
||||
else:
|
||||
sub_menu_data['type'] = sub_menu['type']
|
||||
sub_menu_data['key'] = sub_menu['key']
|
||||
sub_menu_list.append(sub_menu_data)
|
||||
menu_data['sub_button'] = sub_menu_list
|
||||
else:
|
||||
if menu['type'] == 'xml' or menu['type'] == 'sub_button':
|
||||
menu_data['type'] = menu['type']
|
||||
menu_data['url'] = menu['url']
|
||||
else:
|
||||
menu_data['type'] = menu['type']
|
||||
menu_data['key'] = menu['key']
|
||||
button.append(menu_data)
|
||||
menu_json['button'] = button
|
||||
wechat.menu.update(agent_id=self.agentid, menu_data=menu_json)
|
||||
else:
|
||||
raise exceptions.Warning(u"初始化企业号失败或该应用无菜单")
|
||||
|
||||
|
||||
class WechatEnterpriseAppMenu(models.Model):
|
||||
_name = 'we.app.menu'
|
||||
_description = 'Wechat Enterprise App Menu Manage'
|
||||
|
||||
agentid = fields.Many2one('we.app', u'企业应用', required=True)
|
||||
partner_menu_id = fields.Many2one('we.app.menu', u'上级菜单')
|
||||
type = fields.Selection(
|
||||
[('sub_button', u'跳转至子菜单'), ('click', u'点击推事件'), ('xml', u'跳转URL'), ('scancode_push', u'扫码推事件'),
|
||||
('scancode_waitmsg', u'扫码推事件且弹出“消息接收中”提示框'),
|
||||
('pic_sysphoto', u'弹出系统拍照发图'), ('pic_photo_or_album', u'弹出拍照或者相册发图'), ('pic_weixin', u'弹出微信相册发图器'),
|
||||
('location_select', u'弹出地理位置选择器')], u'按钮的类型', required=True, default='xml')
|
||||
name = fields.Char(u'菜单标题', required=True)
|
||||
key = fields.Char(u'菜单KEY值')
|
||||
url = fields.Char(u'网页链接')
|
||||
|
||||
@api.constrains('partner_menu_id', 'name')
|
||||
def _check_menu_name_length(self):
|
||||
if self.name and self.partner_menu_id and len(self.name) > 7:
|
||||
raise Warning(u'二级菜单显示名称不能超过14个字符或7个汉字.')
|
||||
elif self.name and not self.partner_menu_id and len(self.name) > 4:
|
||||
raise Warning(u'一级菜单显示名称不能超过8个字符或4个汉字.')
|
||||
else:
|
||||
return True
|
||||
|
||||
@api.constrains('agentid')
|
||||
def check_menu_number(self):
|
||||
"""
|
||||
取得一个app的一级菜单量
|
||||
:return:
|
||||
"""
|
||||
menus_ids = self.sudo().search([('agentid', '=', self.agentid['name']), ('partner_menu_id', '=', False)])
|
||||
|
||||
if menus_ids and len(menus_ids) > 3:
|
||||
raise Warning(u'公众号的一级菜单数据不能超过3个.')
|
||||
|
||||
return True
|
||||
|
||||
@api.constrains('partner_menu_id')
|
||||
def check_submenu_number(self):
|
||||
"""
|
||||
取得一个一级菜单的子菜单数量
|
||||
:return:
|
||||
"""
|
||||
sub_menus_ids = self.sudo().search(
|
||||
[('partner_menu_id', '=', self.partner_menu_id['name']), ('partner_menu_id', '!=', False)])
|
||||
|
||||
if sub_menus_ids and len(sub_menus_ids) > 5:
|
||||
raise Warning(u'一级菜单的二级子菜单数据不能超过5个.')
|
||||
|
||||
return True
|
||||
62
sg_wechat_enterprise/models/we_conf.py
Normal file
62
sg_wechat_enterprise/models/we_conf.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import logging
|
||||
import requests
|
||||
import time
|
||||
import base64
|
||||
from odoo.http import request
|
||||
from odoo import api, models, exceptions, fields, _
|
||||
from wechatpy.enterprise import WeChatClient
|
||||
from .client import JkmWeChatClient
|
||||
from json import *
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseConfigration(models.Model):
|
||||
_name = 'we.config'
|
||||
|
||||
name = fields.Char(u'名称', required=True, help=u'取个好名字吧!')
|
||||
corp_id = fields.Char(u'企业ID', required=True, help=u'企业ID,必填项')
|
||||
corp_secret = fields.Char(u'通讯录同步Secret', required=True, help=u'通讯录同步Secret,必填项')
|
||||
odoo_app_id = fields.Many2one('we.app', u'Odoo应用', help=u'在企业微信工作台配置的与Odoo进行连接的应用')
|
||||
company_id = fields.Many2one('res.company', string=u'所属公司')
|
||||
|
||||
_sql_constraints = [
|
||||
('code_complete_name_uniq', 'unique (company_id)', '一个所属公司只能定义一个企业微信!')
|
||||
]
|
||||
|
||||
def get_wechat(self, agent_id=None, company_id=1):
|
||||
"""
|
||||
取得wechat app的实例
|
||||
:param agent_id: conf or None (是企业号对象还是应用对象)
|
||||
:return:
|
||||
"""
|
||||
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
|
||||
if agent_id:
|
||||
enterprise_app = self.env['we.app'].sudo().search([('agentid', '=', agent_id)])
|
||||
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise_app.secret)
|
||||
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise.corp_secret)
|
||||
|
||||
def get_odoo_wechat(self, company_id=1):
|
||||
"""
|
||||
取得Odoo wechat app的实例
|
||||
:param agent_id: conf or None (是企业号对象还是应用对象)
|
||||
:return:
|
||||
"""
|
||||
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
|
||||
if enterprise.odoo_app_id:
|
||||
return (JkmWeChatClient(corp_id=enterprise.corp_id, secret=enterprise.odoo_app_id.secret),
|
||||
enterprise.odoo_app_id.agentid)
|
||||
else:
|
||||
raise exceptions.Warning(u'Odoo应用未配置. ')
|
||||
|
||||
def get_wechat_app(self, app_code=None, company_id=1):
|
||||
"""
|
||||
取得wechat app的实例
|
||||
:param app_code: 应用代码
|
||||
:return:
|
||||
"""
|
||||
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
|
||||
if app_code:
|
||||
enterprise_app = self.env['we.app'].sudo().search([('code', '=', app_code)])
|
||||
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise_app.secret)
|
||||
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise.corp_secret)
|
||||
143
sg_wechat_enterprise/models/we_receive_message.py
Normal file
143
sg_wechat_enterprise/models/we_receive_message.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from odoo import api, models, fields
|
||||
import logging
|
||||
from odoo.http import request
|
||||
import datetime
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseReceiveMessage(models.Model):
|
||||
_name = 'we.receive.message'
|
||||
_description = 'Wechat Enterprise Receive Message Manage'
|
||||
|
||||
name = fields.Char(required=True, index=True)
|
||||
ToUserName = fields.Char(u'企业号CorpID')
|
||||
FromUserName = fields.Char(u'成员UserID')
|
||||
CreateTime = fields.Char(u'消息创建时间')
|
||||
MsgId = fields.Char(u'消息id')
|
||||
AgentID = fields.Many2one('we.app', u'企业应用')
|
||||
MsgType = fields.Selection([('text', u'文本'),
|
||||
('voice', u'语音'),
|
||||
('image', u'图片'),
|
||||
('video', u'视频'),
|
||||
('shortvideo', u'短视频'),
|
||||
('location', u'位置'),
|
||||
('link', u'链接'),
|
||||
('subscribe', u'关注'),
|
||||
('unsubscribe', u'取消关注'),
|
||||
('xml', u'自定义菜单链接跳转'),
|
||||
('click', u'自定义菜单点击'),
|
||||
('scan', u'扫描二维码'),
|
||||
('scancode_waitmsg', u'扫描二维码并等待'),
|
||||
('event', u'取消关注')],
|
||||
string=u'消息类型', required=True, default='text')
|
||||
Content = fields.Text(u'文本消息内容')
|
||||
state = fields.Selection([('1', u'未处理'), ('2', u'已处理'), ('3', u'处理失败')], u'状态', default='1')
|
||||
PicUrl = fields.Char(u'图片链接')
|
||||
MediaId = fields.Char(u'图片媒体文件id')
|
||||
Format = fields.Char(u'语音格式')
|
||||
ThumbMediaId = fields.Char(u'视频消息缩略图的媒体id')
|
||||
Location_X = fields.Char(u'地理位置纬度')
|
||||
Location_Y = fields.Char(u'地理位置经度')
|
||||
Scale = fields.Char(u'地图缩放大小')
|
||||
Label = fields.Char(u'地理位置信息')
|
||||
Title = fields.Char(u'标题')
|
||||
Description = fields.Char(u'描述')
|
||||
Cover_PicUrl = fields.Char(u'封面缩略图的url')
|
||||
|
||||
@api.depends('MsgType', 'MsgId')
|
||||
def name_get(self):
|
||||
result = []
|
||||
for receive in self:
|
||||
name = receive.MsgType + '_' + receive.MsgId
|
||||
result.append((receive.id, name))
|
||||
return result
|
||||
|
||||
def add_message(self, data):
|
||||
"""
|
||||
增加一条待处理的上传消息
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
|
||||
app = request.env['we.app'].sudo().search([("agentid", "=", data["AgentID"])])
|
||||
receive_message_data = {
|
||||
'AgentID': app.id,
|
||||
'MsgType': data["MsgType"],
|
||||
'FromUserName': data["FromUserName"],
|
||||
'ToUserName': data["ToUserName"]
|
||||
}
|
||||
current_time = datetime.datetime.now()
|
||||
real_time = current_time + datetime.timedelta(hours=8)
|
||||
receive_message_data["CreateTime"] = real_time
|
||||
receive_message_data["name"] = data["MsgType"] + data["MsgId"]
|
||||
|
||||
if data["MsgType"] == "text":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["Content"] = data["Content"]
|
||||
if data["MsgType"] == "image":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["PicUrl"] = data["PicUrl"]
|
||||
receive_message_data["MediaId"] = data["MediaId"]
|
||||
if data["MsgType"] == "voice":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["MediaId"] = data["MediaId"]
|
||||
receive_message_data["Format"] = data["Format"]
|
||||
if data["MsgType"] == "video" or data["MsgType"] == "shortvideo":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["MediaId"] = data["MediaId"]
|
||||
receive_message_data["ThumbMediaId"] = data["ThumbMediaId"]
|
||||
if data["MsgType"] == "location":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["Location_X"] = data["Location_X"]
|
||||
receive_message_data["Location_Y"] = data["Location_Y"]
|
||||
receive_message_data["Scale"] = data["Scale"]
|
||||
receive_message_data["Label"] = data["Label"]
|
||||
if data["MsgType"] == "link":
|
||||
receive_message_data["MsgId"] = data["MsgId"]
|
||||
receive_message_data["Title"] = data["Title"]
|
||||
receive_message_data["Description"] = data["Description"]
|
||||
receive_message_data["Cover_PicUrl"] = data["PicUrl"]
|
||||
if data["MsgType"] == "event":
|
||||
if data["Event"] == "subscribe":
|
||||
receive_message_data["MsgType"] = "subscribe"
|
||||
if data["Event"] == "unsubscribe":
|
||||
receive_message_data["MsgType"] = "unsubscribe"
|
||||
|
||||
return super(WechatEnterpriseReceiveMessage, self).create(receive_message_data)
|
||||
|
||||
def process_message(self, data):
|
||||
"""
|
||||
处理未处理和失败的消息
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
messages = self.sudo().add_message(data)
|
||||
for message in messages:
|
||||
if message:
|
||||
if message.state == '2':
|
||||
break
|
||||
if data["MsgType"] == "text":
|
||||
process = self.env['we.receive.message.process'].get_message_process(data["MsgType"],
|
||||
data["Content"],
|
||||
data["AgentID"])
|
||||
else:
|
||||
process = self.env['we.receive.message.process'].get_message_process(data["MsgType"],
|
||||
" ",
|
||||
data["AgentID"])
|
||||
try:
|
||||
if process:
|
||||
if data["MsgType"] == "voice" or data["MsgType"] == "image" or data["MsgType"] == "video" or \
|
||||
data["MsgType"] == "shortvideo":
|
||||
process.sudo().exec_class_mothed(data["FromUserName"], data["AgentID"], data["MediaId"])
|
||||
else:
|
||||
process.sudo().exec_class_mothed(data["FromUserName"], data["AgentID"])
|
||||
else:
|
||||
return self.env['we.send.message'].sudo().send_text_message(data["FromUserName"],
|
||||
data["AgentID"],
|
||||
content=u'感谢您的关注!')
|
||||
|
||||
message.sudo().write({'state': '1'})
|
||||
except Exception as e:
|
||||
message.sudo().write({'state': u'处理失败'})
|
||||
raise Warning(u'处理失败, 原因:%s', str(e))
|
||||
109
sg_wechat_enterprise/models/we_receive_message_process.py
Normal file
109
sg_wechat_enterprise/models/we_receive_message_process.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from odoo import models, fields
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseReceiveMessageProcess(models.Model):
|
||||
_name = 'we.receive.message.process'
|
||||
_description = 'Wechat Enterprise Process Receive Process Message'
|
||||
|
||||
name = fields.Char(u'名称', help=u'取个好名称方便管理,比如优惠信息索取', required=True)
|
||||
message_type = fields.Selection([('text', u'文本'),
|
||||
('voice', u'语音'),
|
||||
('image', u'图片'),
|
||||
('video', u'视频'),
|
||||
('shortvideo', u'短视频'),
|
||||
('location', u'位置'),
|
||||
('link', u'链接'),
|
||||
('subscribe', u'关注'),
|
||||
('unsubscribe', u'取消关注'),
|
||||
('xml', u'自定义菜单链接跳转'),
|
||||
('click', u'自定义菜单点击'),
|
||||
('scan', u'扫描二维码'),
|
||||
('scancode_waitmsg', u'扫描二维码并等待'),
|
||||
('unsubscribe', u'取消关注')],
|
||||
string=u'消息类型', required=True)
|
||||
message_key = fields.Char(u'关键字', required=True)
|
||||
class_name = fields.Char(u'负责处理的类', help='此处填写进行处理的类的名称),例如:topro_service_base.test', required=True, default="类的名称")
|
||||
method_name = fields.Char(u'负责处理的方法', help='此处填写进入处理的方法名,方法必须包括参数是message和account_id(微信公众号的id),这是一个dict',
|
||||
required=True, default="方法名")
|
||||
agentID = fields.Many2one('we.app', u'企业应用', required=True)
|
||||
note = fields.Text(u'备注')
|
||||
|
||||
def get_message_process(self, message_type, key_word=False, agent_id=False):
|
||||
"""
|
||||
取得消息处理的设置
|
||||
:param message_type:
|
||||
:param key_word:
|
||||
:param agent_id:
|
||||
:return:
|
||||
"""
|
||||
process = False
|
||||
if message_type:
|
||||
process = self.sudo().search(
|
||||
[('message_type', '=', message_type), ('message_key', '=', key_word), ('agentID', '=', agent_id)],
|
||||
limit=1)
|
||||
if not process and message_type and key_word:
|
||||
process = self.sudo().search([('message_type', '=', message_type), ('message_key', '=', key_word)], limit=1)
|
||||
if not process and message_type:
|
||||
process = self.sudo().search([('message_type', '=', message_type)], limit=1)
|
||||
return process
|
||||
|
||||
def exec_by_message_type(self, message_type, message_key, agent_id):
|
||||
"""
|
||||
根据消息的类型动态调用类进行执行
|
||||
:param message_type:
|
||||
:param message_key:
|
||||
:param agent_id:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# 取得对象,
|
||||
if message_type and message_key:
|
||||
process = self.get_message_process(message_type, message_key, agent_id)
|
||||
process.sudo().exec_class_mothed(message_key, agent_id)
|
||||
|
||||
def exec_class_mothed(self, from_user_name, agent_id, media_id=None):
|
||||
"""
|
||||
执行类的方法
|
||||
:param from_user_name:
|
||||
:param agent_id:
|
||||
:param media_id:
|
||||
:return:
|
||||
"""
|
||||
|
||||
_logger.debug('exec_class_mothed')
|
||||
object_function = getattr(self.env[self.class_name], self.method_name)
|
||||
ret = object_function(from_user_name, agent_id, media_id)
|
||||
|
||||
return ret
|
||||
|
||||
def hello(self, from_user_name, agent_id):
|
||||
"""
|
||||
demo方法,模拟动态处理客户的需求
|
||||
:param from_user_name:
|
||||
:param agent_id:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
self.env['we.send.message'].sudo(). \
|
||||
send_news_message(from_user_name, agent_id, u'测试图文信息', u'测试图文信息的描述',
|
||||
'http://www.baidu.com',
|
||||
'http://www.kia-hnsyt.com.cn/uploads/allimg/141204/1-1412041624240-L.jpg')
|
||||
except Exception as e:
|
||||
_logger.warning(u'发送微信文本失败原因:%s', str(e))
|
||||
raise Warning(str(e))
|
||||
|
||||
def send_img(self, from_user_name, agent_id):
|
||||
"""
|
||||
demo方法,模拟动态处理客户的需求
|
||||
:param from_user_name:
|
||||
:param agent_id:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
self.env['we.send.message'].sudo().send_text_message(from_user_name, agent_id, u'即将为您发送一条消息')
|
||||
except Exception as e:
|
||||
_logger.warning(u'发送微信文本失败原因:%s', str(e))
|
||||
raise Warning(str(e))
|
||||
108
sg_wechat_enterprise/models/we_send_message.py
Normal file
108
sg_wechat_enterprise/models/we_send_message.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from odoo import api, models, exceptions, fields
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseSendMessage(models.Model):
|
||||
_name = 'we.send.message'
|
||||
_description = 'Wechat Enterprise Send Message Manage'
|
||||
|
||||
touser = fields.Many2many('res.users', 'send_message_to_users_ref',
|
||||
'wechat_contacts_id', 'touser', u'成员列表')
|
||||
msgtype = fields.Selection([('text', u'文字消息'), ('image', u'图片消息'), ('voice', u'语音消息'), ('video', u'视频消息'),
|
||||
('file', u'文件消息'), ('news', u'图文消息'), ('mpnews', u'微信后台图文消息')], u'消息类型',
|
||||
required=True, default='text')
|
||||
agentid = fields.Many2one('we.app', u'发送消息的企业应用', required=True)
|
||||
content = fields.Char(u'消息内容')
|
||||
media_id = fields.Char(u'媒体文件')
|
||||
title = fields.Char(u'标题')
|
||||
description = fields.Text(u'描述')
|
||||
articles = fields.Char(u'图文消息')
|
||||
url = fields.Char(u'点击后跳转的链接')
|
||||
picurl = fields.Char(u'图文消息的图片链接')
|
||||
thumb_media_id = fields.Char(u'图文消息缩略图')
|
||||
author = fields.Char(u'图文消息的作者')
|
||||
content_source_url = fields.Char(u'图文消息点击“阅读原文”之后的页面链接')
|
||||
news_content = fields.Char(u'图文消息的内容,支持html标签')
|
||||
digest = fields.Char(u'图文消息的描述')
|
||||
show_cover_pic = fields.Selection([('0', u'否'), ('1', u'是')], u'是否显示封面', default='0')
|
||||
safe = fields.Selection([('0', u'否'), ('1', u'是')], u'是否是保密消息', required=True, default='0')
|
||||
|
||||
def send_message(self):
|
||||
"""
|
||||
发送消息给关注企业号的用户
|
||||
:return:
|
||||
"""
|
||||
users = ""
|
||||
i = 0
|
||||
if self.touser and len(self.touser) > 0:
|
||||
for data in self.touser:
|
||||
i = i + 1
|
||||
if i == len(self.touser):
|
||||
if data['we_employee_id']:
|
||||
users = users + data['we_employee_id']
|
||||
else:
|
||||
if data['we_employee_id']:
|
||||
users = users + data['we_employee_id'] + "|"
|
||||
if users == "":
|
||||
users = '@all'
|
||||
partys = ""
|
||||
|
||||
if self.msgtype == "news":
|
||||
self.send_news_message(users, self.agentid['agentid'], self.title, self.description, self.url, self.picurl)
|
||||
elif self.msgtype == "text":
|
||||
self.send_text_message(users, self.agentid['agentid'], self.content, partys)
|
||||
|
||||
def send_text_message(self, userid, agentid, content, partyid=None):
|
||||
"""
|
||||
发送文本消息给关注企业号的用户
|
||||
:param userid:
|
||||
:param agentid:
|
||||
:param content:
|
||||
:param partyid:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
wechat = self.env['we.config'].sudo().get_wechat(agent_id=agentid)
|
||||
if wechat:
|
||||
data = {
|
||||
'safe': "0",
|
||||
'msgtype': 'text',
|
||||
'agentid': agentid,
|
||||
'touser': userid,
|
||||
'toparty': partyid,
|
||||
'content': content
|
||||
}
|
||||
wechat.message.send_text(agent_id=data['agentid'], user_ids=data['touser'], content=data['content'],
|
||||
party_ids=data['toparty'], safe=data['safe'])
|
||||
else:
|
||||
raise exceptions.Warning(u"初始化企业号失败")
|
||||
except Exception as e:
|
||||
logging.error('send_text_message:%s' % e)
|
||||
|
||||
# 发送图文消息给关注企业号的用户
|
||||
def send_news_message(self, userid, agentid, title=None, description=None, url=None, picurl=None):
|
||||
"""
|
||||
发送图文消息给关注企业号的用户
|
||||
:param userid:
|
||||
:param agentid:
|
||||
:param title:
|
||||
:param description:
|
||||
:param url:
|
||||
:param picurl:
|
||||
:return:
|
||||
"""
|
||||
wechat = self.env['we.config'].sudo().get_wechat(agent_id=agentid)
|
||||
if wechat:
|
||||
articles = [
|
||||
{
|
||||
'url': url,
|
||||
'image': picurl,
|
||||
'description': description,
|
||||
'title': title
|
||||
}
|
||||
]
|
||||
wechat.message.send_articles(agent_id=agentid, user_ids=userid, articles=articles)
|
||||
else:
|
||||
raise exceptions.Warning(u"初始化企业号失败")
|
||||
89
sg_wechat_enterprise/models/we_tools.py
Normal file
89
sg_wechat_enterprise/models/we_tools.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from odoo import api, models, exceptions
|
||||
from odoo.http import request
|
||||
import logging
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import requests
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WechatEnterpriseTools(models.Model):
|
||||
"""
|
||||
微信企业号工具类
|
||||
"""
|
||||
_name = 'we.tools'
|
||||
_description = '微信企业号工具类'
|
||||
|
||||
def get_media(self, media_id):
|
||||
"""
|
||||
通过media_id 获取媒体文件
|
||||
:param media_id: media id
|
||||
:return:
|
||||
"""
|
||||
wechat = self.env['we.config'].sudo().get_wechat()
|
||||
try:
|
||||
media = wechat.media.download(media_id=media_id)
|
||||
return {
|
||||
'errcode': 0,
|
||||
'errmsg': 'ok',
|
||||
'media': media.content
|
||||
}
|
||||
except Exception as ex:
|
||||
_logger.info(u'get media fail, message: {str(ex)}.')
|
||||
return {
|
||||
'errcode': 30001,
|
||||
'errmsg': str(ex)
|
||||
}
|
||||
|
||||
def get_jsapi_ticket(self):
|
||||
"""
|
||||
获取jsapi_ticket
|
||||
:return:
|
||||
"""
|
||||
if request.session.get('ticket') and request.session.get('ticket_time') \
|
||||
and int(time.time()) - request.session['ticket_time'] <= 7000:
|
||||
return {
|
||||
'errcode': 0,
|
||||
'errmsg': 'ok',
|
||||
'ticket': request.session['ticket']
|
||||
}
|
||||
wechat = self.env['we.config'].sudo().get_wechat()
|
||||
get_token = wechat.fetch_access_token()
|
||||
if get_token['errcode'] == 0 and get_token['errmsg'] == 'ok':
|
||||
access_token = get_token['access_token']
|
||||
url = u'https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={access_token}'
|
||||
response = requests.get(url).json()
|
||||
if response['errcode'] == 0 and response['errmsg'] == 'ok':
|
||||
request.session['ticket'] = response['ticket']
|
||||
request.session['ticket_time'] = int(time.time())
|
||||
return response
|
||||
return {
|
||||
"errcode": 10002,
|
||||
"errmsg": "get ticket fail."
|
||||
}
|
||||
|
||||
def check_message_signature(self, message_list, msg_signature):
|
||||
"""
|
||||
校验消息的正确性
|
||||
:param message_list: 消息列表 (list: token, timestamp, nonce, echo_string)
|
||||
:param msg_signature: 签名
|
||||
:return: true or false
|
||||
"""
|
||||
_logger.info(u'check message signature.')
|
||||
message_list.sort()
|
||||
message_str = "".join(message_list)
|
||||
sha1_message = hashlib.sha1(str(message_str).encode('utf-8')).hexdigest()
|
||||
if sha1_message == msg_signature:
|
||||
return True
|
||||
return False
|
||||
|
||||
def decode_echo_str(self, echo_str):
|
||||
"""
|
||||
解密echo string 得到明文内容
|
||||
:param echo_str: 加密字符串
|
||||
:return: message
|
||||
"""
|
||||
_logger.info(u'decode echo string.')
|
||||
base64_str = base64.b64decode(echo_str)
|
||||
Reference in New Issue
Block a user