diff --git a/jikimo_sale_order_message_notify/__init__.py b/jikimo_sale_order_message_notify/__init__.py new file mode 100644 index 00000000..9a7e03ed --- /dev/null +++ b/jikimo_sale_order_message_notify/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/jikimo_sale_order_message_notify/__manifest__.py b/jikimo_sale_order_message_notify/__manifest__.py new file mode 100644 index 00000000..45570c08 --- /dev/null +++ b/jikimo_sale_order_message_notify/__manifest__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': '机企猫智能工厂 消息提醒', + 'version': '1.0', + 'summary': '智能工厂消息提醒模块', + 'sequence': 1, + 'description': """ + + """, + 'category': 'sf', + 'website': 'https://www.sf.jikimo.com', + 'depends': ['jikimo_message_notify'], + 'data': [ + 'security/ir.model.access.csv', + 'data/bussiness_node.xml', + # 'views/sf_message_template_view.xml', + ], + 'test': [ + ], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/jikimo_sale_order_message_notify/data/bussiness_node.xml b/jikimo_sale_order_message_notify/data/bussiness_node.xml new file mode 100644 index 00000000..e6800a48 --- /dev/null +++ b/jikimo_sale_order_message_notify/data/bussiness_node.xml @@ -0,0 +1,9 @@ + + + + + 订单确认 + sale.order + + + \ No newline at end of file diff --git a/jikimo_sale_order_message_notify/models/__init__.py b/jikimo_sale_order_message_notify/models/__init__.py new file mode 100644 index 00000000..c0462bbd --- /dev/null +++ b/jikimo_sale_order_message_notify/models/__init__.py @@ -0,0 +1,2 @@ +from . import jikimo_message_template +from . import sale_order diff --git a/jikimo_sale_order_message_notify/models/jikimo_message_template.py b/jikimo_sale_order_message_notify/models/jikimo_message_template.py new file mode 100644 index 00000000..4c2530f5 --- /dev/null +++ b/jikimo_sale_order_message_notify/models/jikimo_message_template.py @@ -0,0 +1,10 @@ +from odoo import models, fields, api + +class JikimoMessageTemplate(models.Model): + _inherit = "jikimo.message.template" + + def _get_message_model(self): + res = super(JikimoMessageTemplate, self)._get_message_model() + res.append("sale.order") + return res + diff --git a/jikimo_sale_order_message_notify/models/sale_order.py b/jikimo_sale_order_message_notify/models/sale_order.py new file mode 100644 index 00000000..ad0c3209 --- /dev/null +++ b/jikimo_sale_order_message_notify/models/sale_order.py @@ -0,0 +1,11 @@ +from odoo import models, fields, api + + +class SaleOrder(models.Model): + _name = "sale.order" + _inherit = ["sale.order", "jikimo.message.dispatch"] + + def create(self, vals_list): + res = super(SaleOrder, self).create(vals_list) + res.add_queue('订单确认') + return res diff --git a/jikimo_sale_order_message_notify/security/group_security.xml b/jikimo_sale_order_message_notify/security/group_security.xml new file mode 100644 index 00000000..fdbc3ae5 --- /dev/null +++ b/jikimo_sale_order_message_notify/security/group_security.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/jikimo_sale_order_message_notify/security/ir.model.access.csv b/jikimo_sale_order_message_notify/security/ir.model.access.csv new file mode 100644 index 00000000..0b7f9c7b --- /dev/null +++ b/jikimo_sale_order_message_notify/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + + + + + diff --git a/jikimo_sale_order_message_notify/views/sf_message_template_view.xml b/jikimo_sale_order_message_notify/views/sf_message_template_view.xml new file mode 100644 index 00000000..21920b64 --- /dev/null +++ b/jikimo_sale_order_message_notify/views/sf_message_template_view.xml @@ -0,0 +1,76 @@ + + + + + + + + sf.message.template.view.form + message.template + +
+ +
+
+ + + + + + + + + +
+
+
+
+ + + sf.message.template.view.tree + message.template + + + + + + + + + + + + + + + sf.message.template.search.view + message.template + + + + + + + + + + + + 消息模板 + message.template + tree,form + + + + + +
+
\ No newline at end of file diff --git a/sg_wechat_enterprise/.gitignore b/sg_wechat_enterprise/.gitignore deleted file mode 100644 index a81c8ee1..00000000 --- a/sg_wechat_enterprise/.gitignore +++ /dev/null @@ -1,138 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ diff --git a/sg_wechat_enterprise/__init__.py b/sg_wechat_enterprise/__init__.py deleted file mode 100644 index 4589499c..00000000 --- a/sg_wechat_enterprise/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import we_api -from . import models -from . import controllers diff --git a/sg_wechat_enterprise/__manifest__.py b/sg_wechat_enterprise/__manifest__.py deleted file mode 100644 index 883747fd..00000000 --- a/sg_wechat_enterprise/__manifest__.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. -{ - 'name': '企业微信', - 'version': '0.1', - 'summary': '企业通讯录\消息处理\企业应用\无缝登录', - 'sequence': 30, - "author": 'SmartGo Studio.,', - 'description': '''用于企业内部员工的管理, -ER企业微信模块 -===================================================== -主要针对odoo使用微信进行管理,包括以下功能: -1) 公众号信息管理(企业号下多applicaiton管理) -2) 接收消息处理 -3) 发送消息处理 -4) 自定义菜单处理 - -.... -本安装包使用了WechatEnterpriseSDK/wechat_sdk.py,在此表示感谢。 -源代码可以访问github.地址如下:https://github.com/facert/WechatEnterpriseSDK - - ''', - 'category': '基础信息', - 'website': 'https://www.smartgo.cn', - 'depends': ['base', 'mail','hr'], - 'data': [ - 'security/ir.model.access.csv', - 'views/we_config_view.xml', - 'views/we_app_view.xml', - 'views/we_send_message_view.xml', - 'views/we_receive_message_view.xml', - 'views/we_message_process_view.xml', - 'views/we_templates.xml', - # "views/mail_view.xml", - "views/res_users_view.xml", - 'views/menu_view.xml', - # 'views/we_menu.xml', - "data/data.xml" - ], - 'demo': [ - 'demo/we_config_demo.xml', - ], - 'qweb': [ - # "static/src/xml/base.xml", - # "static/src/xml/account_payment.xml", - # "static/src/xml/account_report_backend.xml", - ], - 'installable': True, - 'application': True, - 'auto_install': False, - # 'post_init_hook': '_auto_install_l10n', -} diff --git a/sg_wechat_enterprise/controllers/WXBizMsgCrypt.py b/sg_wechat_enterprise/controllers/WXBizMsgCrypt.py deleted file mode 100644 index 820a9c96..00000000 --- a/sg_wechat_enterprise/controllers/WXBizMsgCrypt.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env python -# -*- encoding:utf-8 -*- - -""" 对企业微信发送给企业后台的消息加解密示例代码. -@copyright: Copyright (c) 1998-2014 Tencent Inc. - -""" -# ------------------------------------------------------------------------ - -import base64 -import string -import random -import hashlib -import time -import struct -from Crypto.Cipher import AES -import xml.etree.cElementTree as ET -import socket -from . import ierror - -""" -关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案 -请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。 -下载后,按照README中的“Installation”小节的提示进行pycrypto安装。 -""" - - -class FormatException(Exception): - pass - - -def throw_exception(message, exception_class=FormatException): - """my define raise exception function""" - raise exception_class(message) - - -class SHA1: - """计算企业微信的消息签名接口""" - - def getSHA1(self, token, timestamp, nonce, encrypt): - """用SHA1算法生成安全签名 - @param token: 票据 - @param timestamp: 时间戳 - @param encrypt: 密文 - @param nonce: 随机字符串 - @return: 安全签名 - """ - try: - sortlist = [token, timestamp, nonce, encrypt] - sortlist.sort() - sha = hashlib.sha1() - sort_str = "".join(sortlist) - sha.update(sort_str.encode('utf-8')) - return ierror.WXBizMsgCrypt_OK, sha.hexdigest() - except Exception as e: - return ierror.WXBizMsgCrypt_ComputeSignature_Error, None - - -class XMLParse: - """提供提取消息格式中的密文及生成回复消息格式的接口""" - - # xml消息模板 - AES_TEXT_RESPONSE_TEMPLATE = """ - - -%(timestamp)s - -""" - - def extract(self, xmltext): - """提取出xml数据包中的加密消息 - @param xmltext: 待提取的xml字符串 - @return: 提取出的加密消息字符串 - """ - try: - xml_tree = ET.fromstring(xmltext) - encrypt = xml_tree.find("Encrypt") - touser_name = xml_tree.find("ToUserName") - return ierror.WXBizMsgCrypt_OK, encrypt.text, touser_name.text - except Exception as e: - return ierror.WXBizMsgCrypt_ParseXml_Error, None, None - - def generate(self, encrypt, signature, timestamp, nonce): - """生成xml消息 - @param encrypt: 加密后的消息密文 - @param signature: 安全签名 - @param timestamp: 时间戳 - @param nonce: 随机字符串 - @return: 生成的xml字符串 - """ - resp_dict = { - 'msg_encrypt': encrypt, - 'msg_signaturet': signature, - 'timestamp': timestamp, - 'nonce': nonce, - } - resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict - return resp_xml - - -class PKCS7Encoder(): - """提供基于PKCS7算法的加解密接口""" - - block_size = 32 - - def encode(self, text): - """ 对需要加密的明文进行填充补位 - @param text: 需要进行填充补位操作的明文 - @return: 补齐明文字符串 - """ - text_length = len(text) - # 计算需要填充的位数 - amount_to_pad = self.block_size - (text_length % self.block_size) - if amount_to_pad == 0: - amount_to_pad = self.block_size - # 获得补位所用的字符 - pad = chr(amount_to_pad) - return text + pad * amount_to_pad - - def decode(self, decrypted): - """删除解密后明文的补位字符 - @param decrypted: 解密后的明文 - @return: 删除补位字符后的明文 - """ - pad = ord(decrypted[-1]) - if pad < 1 or pad > 32: - pad = 0 - return decrypted[:-pad] - - -class Prpcrypt(object): - """提供接收和推送给企业微信消息的加解密接口""" - - def __init__(self, key): - - # self.key = base64.b64decode(key+"=") - self.key = key - # 设置加解密模式为AES的CBC模式 - self.mode = AES.MODE_CBC - - def encrypt(self, text, corpid): - """对明文进行加密 - @param text: 需要加密的明文 - @return: 加密得到的字符串 - """ - # 16位随机字符串添加到明文开头 - text = self.get_random_str() + str(struct.pack("I", socket.htonl(len(text))), encoding='utf8')\ - + str(text, encoding='utf8') + corpid - # 使用自定义的填充方式对明文进行补位填充 - pkcs7 = PKCS7Encoder() - text = pkcs7.encode(text) - # 加密 - cryptor = AES.new(self.key, self.mode, self.key[:16]) - try: - ciphertext = cryptor.encrypt(text) - # 使用BASE64对加密后的字符串进行编码 - return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext) - except Exception as e: - return ierror.WXBizMsgCrypt_EncryptAES_Error, None - - def decrypt(self, text, corpid): - """对解密后的明文进行补位删除 - @param text: 密文 - @return: 删除填充补位后的明文 - """ - try: - cryptor = AES.new(self.key, self.mode, self.key[:16]) - # 使用BASE64对密文进行解码,然后AES-CBC解密 - plain_text = cryptor.decrypt(base64.b64decode(text)) - except Exception as e: - return ierror.WXBizMsgCrypt_DecryptAES_Error, None - try: - pad = plain_text[-1] - # 去掉补位字符串 - # pkcs7 = PKCS7Encoder() - # plain_text = pkcs7.encode(plain_text) - # 去除16位随机字符串 - content = plain_text[16:-pad] - xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0]) - xml_content = content[4: xml_len + 4] - from_corpid = content[xml_len + 4:] - except Exception as e: - return ierror.WXBizMsgCrypt_IllegalBuffer, None - if str(from_corpid, encoding="utf8") != corpid and len(ET.fromstring(xml_content).findall("SuiteId")) < 1: - return ierror.WXBizMsgCrypt_ValidateCorpid_Error, None - return 0, xml_content - - def get_random_str(self): - """ 随机生成16位字符串 - @return: 16位字符串 - """ - rule = string.ascii_letters + string.digits - str = random.sample(rule, 16) - return "".join(str) - - -class WXBizMsgCrypt(object): - # 构造函数 - # @param sToken: 企业微信后台,开发者设置的Token - # @param sEncodingAESKey: 企业微信后台,开发者设置的EncodingAESKey - # @param sCorpId: 企业号的CorpId - def __init__(self, sToken, sEncodingAESKey, sCorpId): - try: - self.key = base64.b64decode(sEncodingAESKey + "=") - assert len(self.key) == 32 - except: - throw_exception("[error]: EncodingAESKey unvalid !", FormatException) - # return ierror.WXBizMsgCrypt_IllegalAesKey,None - self.m_sToken = sToken - self.m_sCorpid = sCorpId - - # 验证URL - # @param sMsgSignature: 签名串,对应URL参数的msg_signature - # @param sTimeStamp: 时间戳,对应URL参数的timestamp - # @param sNonce: 随机串,对应URL参数的nonce - # @param sEchoStr: 随机串,对应URL参数的echostr - # @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效 - # @return:成功0,失败返回对应的错误码 - - def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): - sha1 = SHA1() - ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr) - if ret != 0: - return ret, None - if not signature == sMsgSignature: - return ierror.WXBizMsgCrypt_ValidateSignature_Error, None - pc = Prpcrypt(self.key) - ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sCorpid) - return ret, sReplyEchoStr - - def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None): - # 将企业回复用户的消息加密打包 - # @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串 - # @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间 - # @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce - # sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, - # return:成功0,sEncryptMsg,失败返回对应的错误码None - pc = Prpcrypt(self.key) - ret, encrypt = pc.encrypt(sReplyMsg, self.m_sCorpid) - if ret != 0: - return ret, None - if timestamp is None: - timestamp = str(int(time.time())) - # 生成安全签名 - sha1 = SHA1() - ret, signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt) - if ret != 0: - return ret, None - xmlParse = XMLParse() - return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce) - - def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce): - # 检验消息的真实性,并且获取解密后的明文 - # @param sMsgSignature: 签名串,对应URL参数的msg_signature - # @param sTimeStamp: 时间戳,对应URL参数的timestamp - # @param sNonce: 随机串,对应URL参数的nonce - # @param sPostData: 密文,对应POST请求的数据 - # xml_content: 解密后的原文,当return返回0时有效 - # @return: 成功0,失败返回对应的错误码 - # 验证安全签名 - xmlParse = XMLParse() - ret, encrypt, touser_name = xmlParse.extract(sPostData) - if ret != 0: - return ret, None - sha1 = SHA1() - ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt) - if ret != 0: - return ret, None - if not signature == sMsgSignature: - return ierror.WXBizMsgCrypt_ValidateSignature_Error, None - pc = Prpcrypt(self.key) - ret, xml_content = pc.decrypt(encrypt, self.m_sCorpid) - return ret, xml_content diff --git a/sg_wechat_enterprise/controllers/__init__.py b/sg_wechat_enterprise/controllers/__init__.py deleted file mode 100644 index 0b1b2d96..00000000 --- a/sg_wechat_enterprise/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ - -from . import wechat_enterprise - diff --git a/sg_wechat_enterprise/controllers/ierror.py b/sg_wechat_enterprise/controllers/ierror.py deleted file mode 100644 index 6678fecf..00000000 --- a/sg_wechat_enterprise/controllers/ierror.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################### -# Author: jonyqin -# Created Time: Thu 11 Sep 2014 01:53:58 PM CST -# File Name: ierror.py -# Description:定义错误码含义 -######################################################################### -WXBizMsgCrypt_OK = 0 -WXBizMsgCrypt_ValidateSignature_Error = -40001 -WXBizMsgCrypt_ParseXml_Error = -40002 -WXBizMsgCrypt_ComputeSignature_Error = -40003 -WXBizMsgCrypt_IllegalAesKey = -40004 -WXBizMsgCrypt_ValidateCorpid_Error = -40005 -WXBizMsgCrypt_EncryptAES_Error = -40006 -WXBizMsgCrypt_DecryptAES_Error = -40007 -WXBizMsgCrypt_IllegalBuffer = -40008 -WXBizMsgCrypt_EncodeBase64_Error = -40009 -WXBizMsgCrypt_DecodeBase64_Error = -40010 -WXBizMsgCrypt_GenReturnXml_Error = -40011 diff --git a/sg_wechat_enterprise/controllers/wechat_enterprise.py b/sg_wechat_enterprise/controllers/wechat_enterprise.py deleted file mode 100644 index 0c7f8738..00000000 --- a/sg_wechat_enterprise/controllers/wechat_enterprise.py +++ /dev/null @@ -1,231 +0,0 @@ -import time -import functools -import base64 -from json import * - -from odoo import http, fields -from odoo.http import request -from . import WXBizMsgCrypt -from werkzeug.exceptions import abort -import xml.etree.cElementTree as Et -import requests as req - -import logging -from lxml import etree - - -_logger = logging.getLogger(__name__) - - -def wechat_login(func): - """ - 用来根据userid取得合作伙伴的id - :return: - """ - - @functools.wraps(func) - def wrapper(*args, **kw): - # now_time = time.time() - # request.session['session_time'] = time.time() - # if 'session_time' not in request.session: - # request.session['session_time'] = 1 - # if now_time > request.session['session_time'] + 350: - _logger.info(u"没进来之前的kw值为%s" % JSONEncoder().encode(kw)) - # if not request.session['login'] or not request.session['login'] or request.session[ - # 'login'] == "public": - enterprise, agent_id = request.env['we.config'].sudo().get_odoo_wechat() - if enterprise and agent_id: - if 'code' in kw and 'state' in kw: # 检查是否取得了code - if 'kw' in request.session.keys(): - _logger.info('code:%s' % kw['code']) - account = enterprise.oauth.get_user_info(code=kw['code']) - _logger.info('account:%s' % account) - if account: # 是否取得了微信企业号通讯录的账号 - user_detail = enterprise.user.get_detail(account['user_ticket']) - _logger.info('user_detail:%s' % user_detail) - request.env['we.employee'].we_privacy_update(user_detail) - user = request.env['res.users'].sudo().search( - [('we_employee_id', '=', account['UserId'])]) - _logger.info('user:%s' % user) - if user: # 是否取得了用户 - if 'state' in kw: - state = base64.b64encode(kw['state'].encode('utf-8')).decode() - kw['state'] = state - uid = request.session.authenticate(request.session.db, user.login, - account['UserId']) - kw['user_id'] = uid - request.session['session_time'] = time.time() - request.session['login'] = user.login - - _logger.info(u"进来之后的kw值为%s" % kw) - return func(*args, **kw) - else: - _logger.warning(u'用户不存在.') - return request.render('sg_wechat_enterprise.wechat_warning', - {'title': u'警告', 'content': u'该员工的未配置登录用户.'}) - else: - _logger.warning(u'微信企业号验证失败.') - return request.render('sg_wechat_enterprise.wechat_warning', - {'title': u'警告', 'content': u'微信企业号验证失败.'}) - else: - # 返回时候进入的地方 - del kw['code'] - del kw['state'] - request.session['kw'] = base64.b64encode(JSONEncoder().encode(kw).encode('utf-8')).decode() - if len(kw) == 0: - base_url = request.httprequest.base_url - else: - base_url = request.httprequest.base_url + '?' - for item, value in kw.items(): - base_url += item + '=' + value + "&" - base_url = base_url[: -1] - - url = enterprise.oauth.authorize_url(base_url, - state=base64.b64encode( - JSONEncoder().encode(kw).encode('utf-8')).decode(), - agent_id=agent_id, - scope='snsapi_privateinfo') - _logger.warning(u"这是授权的url:" + url) - value = {"url": url} - return request.render("sg_wechat_enterprise.Transfer", value) - else: # 开始微信企业号登录认证 - request.session['kw'] = base64.b64encode(JSONEncoder().encode(kw).encode('utf-8')).decode() - - if len(kw) == 0: - base_url = request.httprequest.base_url - else: - base_url = request.httprequest.base_url + '?' - for item, value in kw.items(): - base_url += item + '=' + value + "&" - base_url = base_url[: -1] - url = enterprise.oauth.authorize_url(base_url, - state=base64.b64encode( - JSONEncoder().encode(kw).encode('utf-8')).decode(), - agent_id=agent_id, - scope='snsapi_privateinfo' - ) - _logger.warning(u"这是授权的url:" + url) - value = {"url": url} - return request.render("sg_wechat_enterprise.Transfer", value) - else: - _logger.warning(u'微信企业号初始化失败.') - return request.render('sg_wechat_enterprise.wechat_warning', - {'title': u'警告', 'content': u'微信企业号初始化失败.'}) - - # return func(*args, **kw) - - return wrapper - - -class WechatEnterprise(http.Controller): - """ - 用于接收微信发过来的任何消息,并转发给相应的业务类进行处理 - """ - __check_str = 'NDOEHNDSY#$_@$JFDK:Q{!' - BASE_URL = '/we' - - @wechat_login - @http.route(BASE_URL + '/auth', type='http', auth='none') - def auth(self, *args, **kw): - """ - 企业微信免登认证 - """ - try: - # user_id = (request.session['uid']) - redirect1 = kw['redirect'] if 'redirect' in kw else None - uid = kw['user_id'] if 'redirect' in kw else None - _logger.info('user_id %s', uid) - if uid is not False: - request.params['login_success'] = True - if not redirect1: - redirect1 = '/web' - redirect1 = redirect1.replace('-', '&').replace('?', '#') - logging.info('url:%s' % redirect1) - return request.redirect(redirect1) - except Exception as ex: - _logger.error('无有效的登录凭证.') - _logger.warning('auth exceptions:%s' % ex) - return request.render('sg_wechat_enterprise.wechat_warning', - {'title': u'警告', 'content': u'无有效的登录凭证.'}) - - @http.route('/WechatEnterprise//api', type='http', auth="public", methods=["GET", "POST"], csrf=False) - def process(self, code, **kwargs): - """ - 处理从微信服务器发送过来的请求 - :param code: 自定义代码 - :param kwargs: 包含 (msg_signature, timestamp, nonce, echostr) 等参数 - :return: - """ - _logger.info(u'处理从微信服务器发送过来的请求code: %s, kwargs: %s' % (code, kwargs)) - app_id = request.env['we.app'].sudo().search([('code', '=', code)], limit=1) - if not app_id: - _logger.warning(u'Can not find wechat app by code: {code}') - abort(403) - corp_id = app_id.enterprise_id - we_chat_cpt = WXBizMsgCrypt.WXBizMsgCrypt(app_id.Token, app_id.EncodingAESKey, corp_id.corp_id) - signature, timestamp, nonce = kwargs['msg_signature'], kwargs['timestamp'], kwargs['nonce'] - if kwargs.get('echostr'): - echo_string = kwargs['echostr'] - sort_list = [app_id.Token, timestamp, nonce, echo_string] - if request.env['we.tools'].sudo(). \ - check_message_signature(message_list=sort_list, msg_signature=signature): - ret, signature_echo_string = we_chat_cpt.VerifyURL(signature, timestamp, nonce, echo_string) - if ret == 0: - return str(signature_echo_string, encoding="utf8") - body_text = request.httprequest.data - ret, signature_message = we_chat_cpt.DecryptMsg(body_text, signature, timestamp, nonce) - xml_tree = Et.fromstring(signature_message) - if len(xml_tree.findall("SuiteId")) > 0: - return "success" - if len(xml_tree.find("ApprovalInfo")) > 0: - xmlstr = etree.fromstring(signature_message) - # data = xml2json_from_elementtree(xmlstr) - return request.env['we.receive.message'].sudo().sys_approval_change(xml_tree.find("ApprovalInfo")) - data = { - 'MsgType': xml_tree.find("MsgType").text, - # 'AgentID': xml_tree.find("AgentID").text, - 'ToUserName': xml_tree.find("ToUserName").text, - 'FromUserName': xml_tree.find("FromUserName").text, - 'CreateTime': xml_tree.find("CreateTime").text - } - if xml_tree.find("AgentID") != None: - data['AgentID'] = xml_tree.find("AgentID").text - if data["MsgType"] == "text": - data["Content"] = xml_tree.find("Content").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "image": - data["PicUrl"] = xml_tree.find("PicUrl").text - data["MediaId"] = xml_tree.find("MediaId").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "voice": - data["MediaId"] = xml_tree.find("MediaId").text - data["Format"] = xml_tree.find("Format").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "video" or data["MsgType"] == "shortvideo": - data["MediaId"] = xml_tree.find("MediaId").text - data["ThumbMediaId"] = xml_tree.find("ThumbMediaId").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "location": - data["Location_X"] = xml_tree.find("Location_X").text - data["Location_Y"] = xml_tree.find("Location_Y").text - data["Scale"] = xml_tree.find("Scale").text - data["Label"] = xml_tree.find("Label").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "link": - data["Title"] = xml_tree.find("Title").text - data["Description"] = xml_tree.find("Description").text - data["PicUrl"] = xml_tree.find("PicUrl").text - data["MsgId"] = xml_tree.find("MsgId").text - if data["MsgType"] == "event": - if xml_tree.find("Event") == "subscribe" or xml_tree.find("Event") == "unsubscribe": - data["Event"] = xml_tree.find("Event").text - else: - ret, signature_message = we_chat_cpt.EncryptMsg(signature_message, nonce, timestamp) - return signature_message - request.env['we.receive.message'].sudo().process_message(data) - return '' - - @http.route('/WechatEnterprise/transfer', type='http', auth="public", methods=["POST", "GET"], csrf=False) - def transfer(self, url): - value = {"url": url} - return request.render('sg_wechat_enterprise.Transfer', value) diff --git a/sg_wechat_enterprise/data/data.xml b/sg_wechat_enterprise/data/data.xml deleted file mode 100644 index bebcfa10..00000000 --- a/sg_wechat_enterprise/data/data.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - SNS Message To 企业微信 - - code - model.send_we_message() - 1 - minutes - -1 - - - - - - \ No newline at end of file diff --git a/sg_wechat_enterprise/demo/we_config_demo.xml b/sg_wechat_enterprise/demo/we_config_demo.xml deleted file mode 100644 index 605bdbd9..00000000 --- a/sg_wechat_enterprise/demo/we_config_demo.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - SmartGo - wwad4f2c227d490637 - kq_AzJN1FoPdWjyEwAQs_cqzJhALmKhmwYMBQyJzuEs - 1 - - - - - diff --git a/sg_wechat_enterprise/models/__init__.py b/sg_wechat_enterprise/models/__init__.py deleted file mode 100644 index b8fb8a02..00000000 --- a/sg_wechat_enterprise/models/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -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 diff --git a/sg_wechat_enterprise/models/client/__init__.py b/sg_wechat_enterprise/models/client/__init__.py deleted file mode 100644 index 72bca418..00000000 --- a/sg_wechat_enterprise/models/client/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -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() diff --git a/sg_wechat_enterprise/models/client/api/__init__.py b/sg_wechat_enterprise/models/client/api/__init__.py deleted file mode 100644 index f50e1972..00000000 --- a/sg_wechat_enterprise/models/client/api/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .jkm_user import JkmWechatUser -from .jkm_oauth import JkmWechatOauth diff --git a/sg_wechat_enterprise/models/client/api/jkm_oauth.py b/sg_wechat_enterprise/models/client/api/jkm_oauth.py deleted file mode 100644 index 3d491389..00000000 --- a/sg_wechat_enterprise/models/client/api/jkm_oauth.py +++ /dev/null @@ -1,53 +0,0 @@ -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, - } - ) diff --git a/sg_wechat_enterprise/models/client/api/jkm_user.py b/sg_wechat_enterprise/models/client/api/jkm_user.py deleted file mode 100644 index 44a8c616..00000000 --- a/sg_wechat_enterprise/models/client/api/jkm_user.py +++ /dev/null @@ -1,21 +0,0 @@ -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 - } - ) - diff --git a/sg_wechat_enterprise/models/mail.py b/sg_wechat_enterprise/models/mail.py deleted file mode 100644 index 7a0c91b4..00000000 --- a/sg_wechat_enterprise/models/mail.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- 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('//]*//\]\]>',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('')#处理换行 - re_h=re.compile(']*>')#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\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 \ No newline at end of file diff --git a/sg_wechat_enterprise/models/res_users.py b/sg_wechat_enterprise/models/res_users.py deleted file mode 100644 index 275d3bdb..00000000 --- a/sg_wechat_enterprise/models/res_users.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- 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 diff --git a/sg_wechat_enterprise/models/we_app.py b/sg_wechat_enterprise/models/we_app.py deleted file mode 100644 index c4ea91ef..00000000 --- a/sg_wechat_enterprise/models/we_app.py +++ /dev/null @@ -1,213 +0,0 @@ -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 diff --git a/sg_wechat_enterprise/models/we_conf.py b/sg_wechat_enterprise/models/we_conf.py deleted file mode 100644 index 403e900c..00000000 --- a/sg_wechat_enterprise/models/we_conf.py +++ /dev/null @@ -1,62 +0,0 @@ -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) \ No newline at end of file diff --git a/sg_wechat_enterprise/models/we_receive_message.py b/sg_wechat_enterprise/models/we_receive_message.py deleted file mode 100644 index cbeaedcc..00000000 --- a/sg_wechat_enterprise/models/we_receive_message.py +++ /dev/null @@ -1,143 +0,0 @@ -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)) diff --git a/sg_wechat_enterprise/models/we_receive_message_process.py b/sg_wechat_enterprise/models/we_receive_message_process.py deleted file mode 100644 index 02acb682..00000000 --- a/sg_wechat_enterprise/models/we_receive_message_process.py +++ /dev/null @@ -1,109 +0,0 @@ -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)) diff --git a/sg_wechat_enterprise/models/we_send_message.py b/sg_wechat_enterprise/models/we_send_message.py deleted file mode 100644 index 63a06042..00000000 --- a/sg_wechat_enterprise/models/we_send_message.py +++ /dev/null @@ -1,108 +0,0 @@ -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"初始化企业号失败") diff --git a/sg_wechat_enterprise/models/we_tools.py b/sg_wechat_enterprise/models/we_tools.py deleted file mode 100644 index 3dd8fc45..00000000 --- a/sg_wechat_enterprise/models/we_tools.py +++ /dev/null @@ -1,89 +0,0 @@ -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) diff --git a/sg_wechat_enterprise/requirements.txt b/sg_wechat_enterprise/requirements.txt deleted file mode 100644 index c4f4248f..00000000 --- a/sg_wechat_enterprise/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -wechatpy==1.8.6 diff --git a/sg_wechat_enterprise/security/ir.model.access.csv b/sg_wechat_enterprise/security/ir.model.access.csv deleted file mode 100644 index 4a21fc53..00000000 --- a/sg_wechat_enterprise/security/ir.model.access.csv +++ /dev/null @@ -1,13 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink - -access_user_we_app,we_app,model_we_app,,1,1,1,1 - -access_user_we_config,we_config,model_we_config,,1,1,1,1 - -access_user_we_receive_message,we_receive_message,model_we_receive_message,,1,1,1,1 - -access_user_we_receive_message_process,we_receive_message_process,model_we_receive_message_process,,1,1,1,1 - -access_user_we_send_message,we_send_message,model_we_send_message,,1,1,1,1 - -access_user_we_app_menu,we_app_menu,model_we_app_menu,,1,1,1,1 diff --git a/sg_wechat_enterprise/static/css/loading.css b/sg_wechat_enterprise/static/css/loading.css deleted file mode 100644 index dd6243a7..00000000 --- a/sg_wechat_enterprise/static/css/loading.css +++ /dev/null @@ -1,24 +0,0 @@ -@-webkit-keyframes loadingCircle { - to { - transform:rotate(1turn) - } -} -@keyframes loadingCircle { - to { - transform:rotate(1turn) - } -} -.load-box { - position:fixed; - top:calc(50% - 11px); - left:calc(50% - 111px); - display:flex; - align-items:center; -} -.load-icon { - animation:loadingCircle 1s linear infinite; -} -.load-txt { - font-size:16px; - margin-left:10px; -} \ No newline at end of file diff --git a/sg_wechat_enterprise/static/description/icon.png b/sg_wechat_enterprise/static/description/icon.png deleted file mode 100644 index c83caaea..00000000 Binary files a/sg_wechat_enterprise/static/description/icon.png and /dev/null differ diff --git a/sg_wechat_enterprise/static/description/qyh.png b/sg_wechat_enterprise/static/description/qyh.png deleted file mode 100644 index 9c4e4f24..00000000 Binary files a/sg_wechat_enterprise/static/description/qyh.png and /dev/null differ diff --git a/sg_wechat_enterprise/static/js/url_transfers.js b/sg_wechat_enterprise/static/js/url_transfers.js deleted file mode 100644 index d3b61ee1..00000000 --- a/sg_wechat_enterprise/static/js/url_transfers.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Created by jiangxiang on 2016/3/14. - */ - -//��ȡ���Ӵ�������openID���� -function getUrlParam(url, name) { - var pattern = new RegExp("[?&]" + name + "\=([^&]+)", "g"); - var matcher = pattern.exec(url); - var items = null; - if (matcher != null) { - try { - items = decodeURIComponent(decodeURIComponent(matcher[1])); - } catch (e) { - try { - items = decodeURIComponent(matcher[1]); - } catch (e) { - items = matcher[1]; - } - } - } - items = items.replace(/^\s*/, ""); - return items; -} - -var url = document.getElementById("url").innerText; -window.location.href = url; \ No newline at end of file diff --git a/sg_wechat_enterprise/tests/__init__.py b/sg_wechat_enterprise/tests/__init__.py deleted file mode 100644 index 40a96afc..00000000 --- a/sg_wechat_enterprise/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/sg_wechat_enterprise/views/app_view.xml b/sg_wechat_enterprise/views/app_view.xml deleted file mode 100644 index 8243900b..00000000 --- a/sg_wechat_enterprise/views/app_view.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - we.app.form - we.app - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - we.app.tree - we.app - - - - - - - - - - - - - - we.app.search - we.app - primary - - - - - - - - - - 应用配置 - ir.actions.act_window - we.app - tree,form - form - - - -
-
- diff --git a/sg_wechat_enterprise/views/mail_view.xml b/sg_wechat_enterprise/views/mail_view.xml deleted file mode 100644 index 5aee337b..00000000 --- a/sg_wechat_enterprise/views/mail_view.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - mail.message.form.dingtalk - mail.message - - - - - - - - - -
-
-
-
-
-
-
\ No newline at end of file diff --git a/sg_wechat_enterprise/views/menu_view.xml b/sg_wechat_enterprise/views/menu_view.xml deleted file mode 100644 index 781102cc..00000000 --- a/sg_wechat_enterprise/views/menu_view.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/sg_wechat_enterprise/views/res_users_view.xml b/sg_wechat_enterprise/views/res_users_view.xml deleted file mode 100644 index c65d8e46..00000000 --- a/sg_wechat_enterprise/views/res_users_view.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - res.users.account.form - res.users - - - - - - - - - - - - - - diff --git a/sg_wechat_enterprise/views/we_app_view.xml b/sg_wechat_enterprise/views/we_app_view.xml deleted file mode 100644 index 1d85dd14..00000000 --- a/sg_wechat_enterprise/views/we_app_view.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - we.app.form - we.app - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - we.app.tree - we.app - - - - - - - - - - - - - - we.app.search - we.app - primary - - - - - - - - - - 应用配置 - ir.actions.act_window - we.app - tree,form - form - - - - - we.app.menu.form - we.app.menu - -
- - - - - - - - - - - - - - - - -
-
-
- - - - we.app.menu.tree - we.app.menu - - - - - - - - - - - - - - - we.app.menu.search - we.app.menu - primary - - - - - - - - - - 应用菜单 - ir.actions.act_window - we.app.menu - tree,form - form - - - -
-
- diff --git a/sg_wechat_enterprise/views/we_config_view.xml b/sg_wechat_enterprise/views/we_config_view.xml deleted file mode 100644 index 85a970a7..00000000 --- a/sg_wechat_enterprise/views/we_config_view.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - we.config.form - we.config - -
- - - - - - - - - - - -
-
-
- - - - we.config.tree - we.config - - - - - - - - - - - - 企业微信 - ir.actions.act_window - we.config - tree,form - form - - -
-
- diff --git a/sg_wechat_enterprise/views/we_menu.xml b/sg_wechat_enterprise/views/we_menu.xml deleted file mode 100644 index d65c669b..00000000 --- a/sg_wechat_enterprise/views/we_menu.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sg_wechat_enterprise/views/we_message_process_view.xml b/sg_wechat_enterprise/views/we_message_process_view.xml deleted file mode 100644 index 26fef5d0..00000000 --- a/sg_wechat_enterprise/views/we_message_process_view.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - we.receive.message.process.form - we.receive.message.process - -
- - - - - - - - - - - - - - - - - -
-
-
- - - - we.receive.message.process.tree - we.receive.message.process - - - - - - - - - - - - - - - we.receive.message.process.search - we.receive.message.process - primary - - - - - - - - - - 接收消息处理 - ir.actions.act_window - we.receive.message.process - tree,form - form - - - -
-
- diff --git a/sg_wechat_enterprise/views/we_receive_message_view.xml b/sg_wechat_enterprise/views/we_receive_message_view.xml deleted file mode 100644 index 26db7b3e..00000000 --- a/sg_wechat_enterprise/views/we_receive_message_view.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - we.receive.message.form - we.receive.message - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - we.receive.message.tree - we.receive.message - - - - - - - - - - - - - - - - - we.receive.message.search - we.receive.message - primary - - - - - - - - - - 接收消息 - ir.actions.act_window - we.receive.message - tree,form - form - - - -
-
- diff --git a/sg_wechat_enterprise/views/we_send_message_view.xml b/sg_wechat_enterprise/views/we_send_message_view.xml deleted file mode 100644 index e18ff9f7..00000000 --- a/sg_wechat_enterprise/views/we_send_message_view.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - we.send.message.form - we.send.message - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - we.send.message.tree - we.send.message - - - - - - - - - - - - - we.send.message.search - we.send.message - primary - - - - - - - - - - - - 发送消息 - ir.actions.act_window - we.send.message - tree,form - form - - - -
-
- diff --git a/sg_wechat_enterprise/views/we_templates.xml b/sg_wechat_enterprise/views/we_templates.xml deleted file mode 100644 index 6694833a..00000000 --- a/sg_wechat_enterprise/views/we_templates.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/sg_wechat_enterprise/we_api/__init__.py b/sg_wechat_enterprise/we_api/__init__.py deleted file mode 100644 index e4a50359..00000000 --- a/sg_wechat_enterprise/we_api/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import absolute_import, unicode_literals -import logging -try: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) -except ImportError: - from pkg_resources import declare_namespace - declare_namespace(__name__) - -from wechatpy.parser import parse_message # NOQA -from wechatpy.replies import create_reply # NOQA -from wechatpy.client import WeChatClient # NOQA -from wechatpy.exceptions import WeChatException # NOQA -from wechatpy.exceptions import WeChatClientException # NOQA -from wechatpy.oauth import WeChatOAuth # NOQA -from wechatpy.exceptions import WeChatOAuthException # NOQA -from wechatpy.pay import WeChatPay # NOQA -from wechatpy.exceptions import WeChatPayException # NOQA -from wechatpy.component import WeChatComponent # NOQA - - -__version__ = '1.3.1' -__author__ = 'messense' - -# Set default logging handler to avoid "No handler found" warnings. -try: # Python 2.7+ - from logging import NullHandler -except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - -logging.getLogger(__name__).addHandler(NullHandler()) diff --git a/sg_wechat_enterprise/we_api/_compat.py b/sg_wechat_enterprise/we_api/_compat.py deleted file mode 100644 index d2889243..00000000 --- a/sg_wechat_enterprise/we_api/_compat.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy._compat - ~~~~~~~~~~~~~~~~~ - - This module makes it easy for wechatpy to run on both Python 2 and 3. - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import sys -import six -import warnings - -warnings.warn("Module `wechatpy._compat` is deprecated, will be removed in 2.0" - "use `wechatpy.utils` instead", - DeprecationWarning, stacklevel=2) - -from wechatpy.utils import get_querystring -from wechatpy.utils import json diff --git a/sg_wechat_enterprise/we_api/client/__init__.py b/sg_wechat_enterprise/we_api/client/__init__.py deleted file mode 100644 index 02c6c525..00000000 --- a/sg_wechat_enterprise/we_api/client/__init__.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -try: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) -except ImportError: - from pkg_resources import declare_namespace - declare_namespace(__name__) - -import time - -from wechatpy.client.base import BaseWeChatClient -from wechatpy.client import api - - -class WeChatClient(BaseWeChatClient): - - """ - 微信 API 操作类 - 通过这个类可以操作微信 API,发送主动消息、群发消息和创建自定义菜单等。 - """ - - API_BASE_URL = 'https://api.weixin.qq.com/cgi-bin/' - - menu = api.WeChatMenu() - user = api.WeChatUser() - group = api.WeChatGroup() - media = api.WeChatMedia() - card = api.WeChatCard() - qrcode = api.WeChatQRCode() - message = api.WeChatMessage() - misc = api.WeChatMisc() - merchant = api.WeChatMerchant() - customservice = api.WeChatCustomService() - datacube = api.WeChatDataCube() - jsapi = api.WeChatJSAPI() - material = api.WeChatMaterial() - semantic = api.WeChatSemantic() - shakearound = api.WeChatShakeAround() - device = api.WeChatDevice() - template = api.WeChatTemplate() - poi = api.WeChatPoi() - wifi = api.WeChatWiFi() - scan = api.WeChatScan() - - def __init__(self, appid, secret, access_token=None, - session=None, timeout=None, auto_retry=True): - super(WeChatClient, self).__init__( - appid, access_token, session, timeout, auto_retry - ) - self.appid = appid - self.secret = secret - - def fetch_access_token(self): - """ - 获取 access token - 详情请参考 http://mp.weixin.qq.com/wiki/index.php?title=通用接口文档 - - :return: 返回的 JSON 数据包 - """ - return self._fetch_access_token( - url='https://api.weixin.qq.com/cgi-bin/token', - params={ - 'grant_type': 'client_credential', - 'appid': self.appid, - 'secret': self.secret - } - ) - - -class WeChatComponentClient(WeChatClient): - - """ - 开放平台代公众号调用客户端 - """ - - def __init__(self, appid, component, access_token=None, - refresh_token=None, session=None, timeout=None): - # 未用到secret,所以这里没有 - super(WeChatComponentClient, self).__init__( - appid, '', access_token, session, timeout - ) - self.appid = appid - self.component = component - # 如果公众号是刚授权,外部还没有缓存access_token和refresh_token - # 可以传入这两个值,session 会缓存起来。 - # 如果外部已经缓存,这里只需要传入 appid,component和session即可 - if access_token: - self.session.set(self.access_token_key, access_token, 7200) - if refresh_token: - self.session.set(self.refresh_token_key, refresh_token, 7200) - - @property - def access_token_key(self): - return '{0}_access_token'.format(self.appid) - - @property - def refresh_token_key(self): - return '{0}_refresh_token'.format(self.appid) - - @property - def access_token(self): - access_token = self.session.get(self.access_token_key) - if not access_token: - self.fetch_access_token() - access_token = self.session.get(self.access_token_key) - return access_token - - @property - def refresh_token(self): - return self.session.get(self.refresh_token_key) - - def fetch_access_token(self): - """ - 获取 access token - 详情请参考 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list\ - &t=resource/res_list&verify=1&id=open1419318587&token=&lang=zh_CN - - 这是内部刷新机制。请不要完全依赖! - 因为有可能在缓存期间没有对此公众号的操作,造成refresh_token失效。 - - :return: 返回的 JSON 数据包 - """ - expires_in = 7200 - result = self.component.refresh_authorizer_token( - self.appid, self.refresh_token) - if 'expires_in' in result: - expires_in = result['expires_in'] - self.session.set( - self.access_token_key, - result['authorizer_access_token'], - expires_in - ) - self.session.set( - self.refresh_token_key, - result['authorizer_refresh_token'], - expires_in - ) - self.expires_at = int(time.time()) + expires_in - return result diff --git a/sg_wechat_enterprise/we_api/client/api/__init__.py b/sg_wechat_enterprise/we_api/client/api/__init__.py deleted file mode 100644 index 30291577..00000000 --- a/sg_wechat_enterprise/we_api/client/api/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.client.api.menu import WeChatMenu # NOQA -from wechatpy.client.api.user import WeChatUser # NOQA -from wechatpy.client.api.card import WeChatCard # NOQA -from wechatpy.client.api.group import WeChatGroup # NOQA -from wechatpy.client.api.media import WeChatMedia # NOQA -from wechatpy.client.api.message import WeChatMessage # NOQA -from wechatpy.client.api.qrcode import WeChatQRCode # NOQA -from wechatpy.client.api.misc import WeChatMisc # NOQA -from wechatpy.client.api.merchant import WeChatMerchant # NOQA -from wechatpy.client.api.customservice import WeChatCustomService # NOQA -from wechatpy.client.api.datacube import WeChatDataCube # NOQA -from wechatpy.client.api.jsapi import WeChatJSAPI # NOQA -from wechatpy.client.api.material import WeChatMaterial # NOQA -from wechatpy.client.api.semantic import WeChatSemantic # NOQA -from wechatpy.client.api.shakearound import WeChatShakeAround # NOQA -from wechatpy.client.api.device import WeChatDevice # NOQA -from wechatpy.client.api.template import WeChatTemplate # NOQA -from wechatpy.client.api.poi import WeChatPoi # NOQA -from wechatpy.client.api.wifi import WeChatWiFi # NOQA -from wechatpy.client.api.scan import WeChatScan # NOQA diff --git a/sg_wechat_enterprise/we_api/client/api/base.py b/sg_wechat_enterprise/we_api/client/api/base.py deleted file mode 100644 index 790d9414..00000000 --- a/sg_wechat_enterprise/we_api/client/api/base.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - - -class BaseWeChatAPI(object): - - API_BASE_URL = '' - - """ WeChat API base class """ - def __init__(self, client=None): - self._client = client - - def _get(self, url, **kwargs): - if getattr(self, 'API_BASE_URL', None): - kwargs['api_base_url'] = self.API_BASE_URL - return self._client.get(url, **kwargs) - - def _post(self, url, **kwargs): - if getattr(self, 'API_BASE_URL', None): - kwargs['api_base_url'] = self.API_BASE_URL - return self._client.post(url, **kwargs) - - @property - def access_token(self): - return self._client.access_token - - @property - def session(self): - return self._client.session diff --git a/sg_wechat_enterprise/we_api/client/api/card.py b/sg_wechat_enterprise/we_api/client/api/card.py deleted file mode 100644 index 5f6ebe35..00000000 --- a/sg_wechat_enterprise/we_api/client/api/card.py +++ /dev/null @@ -1,431 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatCard(BaseWeChatAPI): - - API_BASE_URL = 'https://api.weixin.qq.com/' - - def create(self, card_data): - """ - 创建卡券 - - :param card_data: 卡券信息 - :return: 创建的卡券 ID - """ - result = self._post( - 'card/create', - data=card_data, - result_processor=lambda x: x['card_id'] - ) - return result - - def batch_add_locations(self, location_data): - """ - 批量导入门店信息 - - :param location_data: 门店信息 - :return: 门店 ID 列表,插入失败的门店元素值为 -1 - """ - result = self._post( - 'card/location/batchadd', - data=location_data, - result_processor=lambda x: x['location_id_list'] - ) - return result - - def batch_get_locations(self, offset=0, count=0): - """ - 批量获取门店信息 - """ - return self._post( - 'card/location/batchget', - data={ - 'offset': offset, - 'count': count - } - ) - - def get_colors(self): - """ - 获得卡券的最新颜色列表,用于创建卡券 - :return: 颜色列表 - """ - result = self._get( - 'card/getcolors', - result_processor=lambda x: x['colors'] - ) - return result - - def create_qrcode(self, qrcode_data): - """ - 创建卡券二维码 - - :param qrcode_data: 二维码信息 - :return: 二维码 ticket,可使用 :func:show_qrcode 换取二维码文件 - """ - result = self._post( - 'card/qrcode/create', - data=qrcode_data, - result_processor=lambda x: x['ticket'] - ) - return result - - def create_landingpage(self, buffer_data): - """ - 创建货架 - """ - result = self._post( - 'card/landingpage/create', - data=buffer_data - ) - return result - - def get_html(self, card_id): - """ - 图文消息群发卡券 - """ - result = self._post( - 'card/mpnews/gethtml', - data={ - 'card_id': card_id - }, - result_processor=lambda x: x['content'] - ) - return result - - def consume_code(self, code, card_id=None): - """ - 消耗 code - """ - card_data = { - 'code': code - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/code/consume', - data=card_data - ) - - def decrypt_code(self, encrypt_code): - """ - 解码加密的 code - """ - result = self._post( - 'card/code/decrypt', - data={ - 'encrypt_code': encrypt_code - }, - result_processor=lambda x: x['code'] - ) - return result - - def delete(self, card_id): - """ - 删除卡券 - """ - return self._post( - 'card/delete', - data={ - 'card_id': card_id - } - ) - - def get_code(self, code, card_id=None, check_consume=True): - """ - 查询 code 信息 - """ - card_data = { - 'code': code - } - if card_id: - card_data['card_id'] = card_id - if not check_consume: - card_data['check_consume'] = check_consume - return self._post( - 'card/code/get', - data=card_data - ) - - def get_card_list(self, openid, card_id=None): - """ - 用于获取用户卡包里的,属于该appid下的卡券。 - """ - card_data = { - 'openid': openid - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/user/getcardlist', - data=card_data - ) - - def batch_get(self, offset=0, count=50, status_list=None): - """ - 批量查询卡券信息 - """ - card_data = { - 'offset': offset, - 'count': count - } - if status_list: - card_data['status_list'] = status_list - return self._post( - 'card/batchget', - data=card_data - ) - - def get(self, card_id): - """ - 查询卡券详情 - """ - result = self._post( - 'card/get', - data={ - 'card_id': card_id - }, - result_processor=lambda x: x['card'] - ) - return result - - def update_code(self, card_id, old_code, new_code): - """ - 更新卡券 code - """ - return self._post( - 'card/code/update', - data={ - 'card_id': card_id, - 'code': old_code, - 'new_code': new_code - } - ) - - def invalid_code(self, code, card_id=None): - """ - 设置卡券失效 - """ - card_data = { - 'code': code - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/code/unavailable', - data=card_data - ) - - def update(self, card_data): - """ - 更新卡券信息 - """ - return self._post( - 'card/update', - data=card_data - ) - - def set_paycell(self, card_id, is_open): - """ - 更新卡券信息 - """ - return self._post( - 'card/paycell/set', - data={ - 'card_id': card_id, - 'is_open': is_open - } - ) - - def set_test_whitelist(self, openids=None, usernames=None): - """ - 设置卡券测试用户白名单 - """ - openids = openids or [] - usernames = usernames or [] - return self._post( - 'card/testwhitelist/set', - data={ - 'openid': openids, - 'username': usernames - } - ) - - def activate_membercard(self, membership_number, code, init_bonus=0, - init_balance=0, card_id=None): - """ - 激活/绑定会员卡 - """ - card_data = { - 'membership_number': membership_number, - 'code': code, - 'init_bonus': init_bonus, - 'init_balance': init_balance - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/membercard/activate', - data=card_data - ) - - def update_membercard(self, code, add_bonus=0, record_bonus='', - add_balance=0, record_balance='', card_id=None): - """ - 会员卡交易更新信息 - """ - card_data = { - 'code': code, - 'add_bonus': add_bonus, - 'add_balance': add_balance, - 'record_bonus': record_bonus, - 'record_balance': record_balance - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/membercard/updateuser', - data=card_data - ) - - def update_movie_ticket(self, code, ticket_class, show_time, duration, - screening_room, seat_number, card_id=None): - """ - 更新电影票 - """ - ticket = { - 'code': code, - 'ticket_class': ticket_class, - 'show_time': show_time, - 'duration': duration, - 'screening_room': screening_room, - 'seat_number': seat_number - } - if card_id: - ticket['card_id'] = card_id - return self._post( - 'card/movieticket/updateuser', - data=ticket - ) - - def checkin_boardingpass(self, code, passenger_name, seat_class, - etkt_bnr, seat='', gate='', boarding_time=None, - is_cancel=False, qrcode_data=None, card_id=None): - """ - 飞机票接口 - """ - data = { - 'code': code, - 'passenger_name': passenger_name, - 'class': seat_class, - 'etkt_bnr': etkt_bnr, - 'seat': seat, - 'gate': gate, - 'is_cancel': is_cancel - } - if boarding_time: - data['boarding_time'] = boarding_time - if qrcode_data: - data['qrcode_data'] = qrcode_data - if card_id: - data['card_id'] = card_id - return self._post( - 'card/boardingpass/checkin', - data=data - ) - - def update_luckymoney_balance(self, code, balance, card_id=None): - """ - 更新红包余额 - """ - card_data = { - 'code': code, - 'balance': balance - } - if card_id: - card_data['card_id'] = card_id - return self._post( - 'card/luckymoney/updateuserbalance', - data=card_data - ) - - def get_redirect_url(self, url, encrypt_code, card_id): - """ - 获取卡券跳转外链 - """ - from wechatpy.utils import WeChatSigner - - code = self.decrypt_code(encrypt_code) - - signer = WeChatSigner() - signer.add_data(self.secret) - signer.add_data(code) - signer.add_data(card_id) - signature = signer.signature - - r = '{url}?encrypt_code={code}&card_id={card_id}&signature={signature}' - return r.format( - url=url, - code=encrypt_code, - card_id=card_id, - signature=signature - ) - - def deposit_code(self, card_id, codes): - """ - 导入code - """ - card_data = { - 'card_id': card_id, - 'code': codes - } - return self._post( - 'card/code/deposit', - data=card_data - ) - - def get_deposit_count(self, card_id): - """ - 查询导入code数目 - """ - card_data = { - 'card_id': card_id, - } - return self._post( - 'card/code/getdepositcount', - data=card_data - ) - - def check_code(self, card_id, codes): - """ - 核查code - """ - card_data = { - 'card_id': card_id, - 'code': codes - } - return self._post( - 'card/code/checkcode', - data=card_data - ) - - def modify_stock(self, card_id, n): - """ - 修改库存 - """ - if n == 0: - return - card_data = { - 'card_id': card_id, - } - if n > 0: - card_data['increase_stock_value'] = n - elif n < 0: - card_data['reduce_stock_value'] = -n - return self._post( - 'card/modifystock', - data=card_data - ) diff --git a/sg_wechat_enterprise/we_api/client/api/customservice.py b/sg_wechat_enterprise/we_api/client/api/customservice.py deleted file mode 100644 index 0a2db30a..00000000 --- a/sg_wechat_enterprise/we_api/client/api/customservice.py +++ /dev/null @@ -1,242 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import hashlib -import time -import datetime - -from six.moves.urllib.parse import quote -from optionaldict import optionaldict -from wechatpy.utils import to_binary -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatCustomService(BaseWeChatAPI): - - def add_account(self, account, nickname, password): - """ - 添加客服账号 - 详情请参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :param account: 完整客服账号,格式为:账号前缀@公众号微信号 - :param nickname: 客服昵称,最长6个汉字或12个英文字符 - :param password: 客服账号登录密码 - :return: 返回的 JSON 数据包 - """ - password = to_binary(password) - password = hashlib.md5(password).hexdigest() - return self._post( - 'https://api.weixin.qq.com/customservice/kfaccount/add', - data={ - 'kf_account': account, - 'nickname': nickname, - 'password': password - } - ) - - def update_account(self, account, nickname, password): - """ - 更新客服账号 - 详情请参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :param account: 完整客服账号,格式为:账号前缀@公众号微信号 - :param nickname: 客服昵称,最长6个汉字或12个英文字符 - :param password: 客服账号登录密码 - :return: 返回的 JSON 数据包 - """ - password = to_binary(password) - password = hashlib.md5(password).hexdigest() - return self._post( - 'https://api.weixin.qq.com/customservice/kfaccount/update', - data={ - 'kf_account': account, - 'nickname': nickname, - 'password': password - } - ) - - def delete_account(self, account): - """ - 删除客服账号 - 详情请参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :param account: 完整客服账号,格式为:账号前缀@公众号微信号 - :return: 返回的 JSON 数据包 - """ - params_data = [ - 'access_token={0}'.format(quote(self.access_token)), - 'kf_account={0}'.format(quote(to_binary(account), safe=b'/@')), - ] - params = '&'.join(params_data) - return self._get( - 'https://api.weixin.qq.com/customservice/kfaccount/del', - params=params - ) - - def get_accounts(self): - """ - 获取客服账号列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :return: 客服账号列表 - """ - res = self._get( - 'customservice/getkflist', - result_processor=lambda x: x['kf_list'] - ) - return res - - def upload_headimg(self, account, media_file): - """ - 上传客服账号头像 - 详情请参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :param account: 完整客服账号 - :param media_file: 要上传的头像文件,一个 File-Object - :return: 返回的 JSON 数据包 - """ - return self._post( - 'https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg', - params={ - 'kf_account': account - }, - files={ - 'media': media_file - } - ) - - def get_online_accounts(self): - """ - 获取在线客服接待信息 - 详情请参考 - http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html - - :return: 客服接待信息列表 - """ - res = self._get( - 'customservice/getonlinekflist', - result_processor=lambda x: x['kf_online_list'] - ) - return res - - def create_session(self, openid, account, text=None): - """ - 多客服创建会话 - 详情请参考 - http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html - - :param openid: 客户 openid - :param account: 完整客服账号 - :param text: 附加信息,可选 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - openid=openid, - kf_account=account, - text=text - ) - return self._post( - 'https://api.weixin.qq.com/customservice/kfsession/create', - data=data - ) - - def close_session(self, openid, account, text=None): - """ - 多客服关闭会话 - 详情请参考 - http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html - - :param openid: 客户 openid - :param account: 完整客服账号 - :param text: 附加信息,可选 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - openid=openid, - kf_account=account, - text=text - ) - return self._post( - 'https://api.weixin.qq.com/customservice/kfsession/close', - data=data - ) - - def get_session(self, openid): - """ - 获取客户的会话状态 - 详情请参考 - http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html - - :param openid: 客户 openid - :return: 返回的 JSON 数据包 - """ - return self._get( - 'https://api.weixin.qq.com/customservice/kfsession/getsession', - params={'openid': openid} - ) - - def get_session_list(self, account): - """ - 获取客服的会话列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html - - :param account: 完整客服账号 - :return: 客服的会话列表 - """ - res = self._get( - 'https://api.weixin.qq.com/customservice/kfsession/getsessionlist', - params={'kf_account': account}, - result_processor=lambda x: x['sessionlist'] - ) - return res - - def get_wait_case(self): - """ - 获取未接入会话列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html - - :return: 返回的 JSON 数据包 - """ - return self._get( - 'https://api.weixin.qq.com/customservice/kfsession/getwaitcase' - ) - - def get_records(self, start_time, end_time, page_index, - page_size=10, user_id=None): - """ - 获取客服聊天记录 - 详情请参考 - http://mp.weixin.qq.com/wiki/19/7c129ec71ddfa60923ea9334557e8b23.html - - :param start_time: 查询开始时间,UNIX 时间戳 - :param end_time: 查询结束时间,UNIX 时间戳,每次查询不能跨日查询 - :param page_index: 查询第几页,从 1 开始 - :param page_size: 每页大小,每页最多拉取 1000 条 - :param user_id: 普通用户的标识,对当前公众号唯一 - - :return: 返回的 JSON 数据包 - """ - if isinstance(start_time, datetime.datetime): - start_time = time.mktime(start_time.timetuple()) - if isinstance(end_time, datetime.datetime): - end_time = time.mktime(end_time.timetuple()) - record_data = { - 'starttime': int(start_time), - 'endtime': int(end_time), - 'pageindex': page_index, - 'pagesize': page_size - } - if user_id: - record_data['openid'] = user_id - res = self._post( - 'https://api.weixin.qq.com/customservice/msgrecord/getrecord', - data=record_data, - result_processor=lambda x: x['recordlist'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/datacube.py b/sg_wechat_enterprise/we_api/client/api/datacube.py deleted file mode 100644 index f6d679c8..00000000 --- a/sg_wechat_enterprise/we_api/client/api/datacube.py +++ /dev/null @@ -1,360 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import datetime - -import six - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatDataCube(BaseWeChatAPI): - - API_BASE_URL = 'https://api.weixin.qq.com/datacube/' - - @classmethod - def _to_date_str(cls, date): - if isinstance(date, (datetime.datetime, datetime.date)): - return date.strftime('%Y-%m-%d') - elif isinstance(date, six.string_types): - return date - else: - raise ValueError('Can not convert %s type to str', type(date)) - - def get_user_summary(self, begin_date, end_date): - """ - 获取用户增减数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getusersummary', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - } - ) - return res['list'] - - def get_user_cumulate(self, begin_date, end_date): - """ - 获取累计用户数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getusercumulate', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_interface_summary(self, begin_date, end_date): - """ - 获取接口分析数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/30ed81ae38cf4f977194bf1a5db73668.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getinterfacesummary', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_interface_summary_hour(self, begin_date, end_date): - """ - 获取接口分析分时数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/30ed81ae38cf4f977194bf1a5db73668.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getinterfacesummaryhour', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_article_summary(self, begin_date, end_date): - """ - 获取图文群发每日数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getarticlesummary', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_article_total(self, begin_date, end_date): - """ - 获取图文群发总数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getarticletotal', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_user_read(self, begin_date, end_date): - """ - 获取图文统计数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getuserread', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_user_read_hour(self, begin_date, end_date): - """ - 获取图文分时统计数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getuserreadhour', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_user_share(self, begin_date, end_date): - """ - 获取图文分享转发数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getusershare', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_user_share_hour(self, begin_date, end_date): - """ - 获取图文分享转发分时数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getusersharehour', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg(self, begin_date, end_date): - """ - 获取消息发送概况数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsg', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_hour(self, begin_date, end_date): - """ - 获取消息发送分时数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsghour', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_week(self, begin_date, end_date): - """ - 获取消息发送周数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsgweek', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_month(self, begin_date, end_date): - """ - 获取消息发送月数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsgmonth', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_dist(self, begin_date, end_date): - """ - 获取消息发送分布数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsgdist', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_dist_week(self, begin_date, end_date): - """ - 获取消息发送分布数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsgdistweek', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res - - def get_upstream_msg_dist_month(self, begin_date, end_date): - """ - 获取消息发送分布数据 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html - - :param begin_date: 起始日期 - :param end_date: 结束日期 - :return: 统计数据列表 - """ - res = self._post( - 'getupstreammsgdistmonth', - data={ - 'begin_date': self._to_date_str(begin_date), - 'end_date': self._to_date_str(end_date) - }, - result_processor=lambda x: x['list'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/device.py b/sg_wechat_enterprise/we_api/client/api/device.py deleted file mode 100644 index 56b5a133..00000000 --- a/sg_wechat_enterprise/we_api/client/api/device.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import base64 -import urllib - -from wechatpy.utils import to_text, to_binary -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatDevice(BaseWeChatAPI): - - API_BASE_URL = 'https://api.weixin.qq.com/device/' - - def send_message(self, device_type, device_id, user_id, content): - """ - 主动发送消息给设备 - 详情请参考 - http://iot.weixin.qq.com/document-2_3.html - - :param device_type: 设备类型,目前为“公众账号原始ID” - :param device_id: 设备ID - :param user_id: 微信用户账号的openid - :param content: 消息内容,BASE64编码 - :return: 返回的 JSON 数据包 - """ - content = to_text(base64.b64encode(to_binary(content))) - return self._post( - 'transmsg', - data={ - 'device_type': device_type, - 'device_id': device_id, - 'openid': user_id, - 'content': content - } - ) - - def create_qrcode(self, device_ids): - """ - 获取设备二维码 - 详情请参考 - http://iot.weixin.qq.com/document-2_5.html - - :param device_ids: 设备id的列表 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'create_qrcode', - data={ - 'device_num': len(device_ids), - 'device_id_list': device_ids - } - ) - - def get_qrcode_url(self, ticket, data=None): - """ - 通过 ticket 换取二维码地址 - 详情请参考 - http://iot.weixin.qq.com/document-2_5.html - - :param ticket: 二维码 ticket - :param data: 额外数据 - :return: 二维码地址 - """ - url = 'http://we.qq.com/d/{ticket}'.format(ticket=ticket) - if data: - if isinstance(data, (dict, tuple, list)): - data = urllib.urlencode(data) - data = to_text(base64.b64encode(to_binary(data))) - url = '{base}#{data}'.format(base=url, data=data) - return url - - def bind(self, ticket, device_id, user_id): - """ - 绑定设备 - 详情请参考 - http://iot.weixin.qq.com/document-2_12.html - - :param ticket: 绑定操作合法性的凭证(由微信后台生成,第三方H5通过客户端jsapi获得) - :param device_id: 设备id - :param user_id: 用户对应的openid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'bind', - data={ - 'ticket': ticket, - 'device_id': device_id, - 'openid': user_id - } - ) - - def unbind(self, ticket, device_id, user_id): - """ - 解绑设备 - 详情请参考 - http://iot.weixin.qq.com/document-2_12.html - - :param ticket: 绑定操作合法性的凭证(由微信后台生成,第三方H5通过客户端jsapi获得) - :param device_id: 设备id - :param user_id: 用户对应的openid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'unbind', - data={ - 'ticket': ticket, - 'device_id': device_id, - 'openid': user_id - } - ) - - def compel_bind(self, device_id, user_id): - """ - 强制绑定用户和设备 - 详情请参考 - http://iot.weixin.qq.com/document-2_12.html - - :param device_id: 设备id - :param user_id: 用户对应的openid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'compel_bind', - data={ - 'device_id': device_id, - 'openid': user_id - } - ) - - force_bind = compel_bind - - def compel_unbind(self, device_id, user_id): - """ - 强制解绑用户和设备 - 详情请参考 - http://iot.weixin.qq.com/document-2_12.html - - :param device_id: 设备id - :param user_id: 用户对应的openid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'compel_unbind', - data={ - 'device_id': device_id, - 'openid': user_id - } - ) - - force_unbind = compel_unbind - - def get_stat(self, device_id): - """ - 设备状态查询 - 详情请参考 - http://iot.weixin.qq.com/document-2_7.html - - :param device_id: 设备id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'get_stat', - data={'device_id': device_id} - ) - - def verify_qrcode(self, ticket): - """ - 验证二维码 - 详情请参考 - http://iot.weixin.qq.com/document-2_9.html - - :param ticket: 设备二维码的ticket - :return: 返回的 JSON 数据包 - """ - return self._post( - 'verify_qrcode', - data={'ticket': ticket} - ) - - def get_user_id(self, device_type, device_id): - """ - 获取设备绑定openID - 详情请参考 - http://iot.weixin.qq.com/document-2_4.html - - :param device_type: 设备类型,目前为“公众账号原始ID” - :param device_id: 设备id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'get_openid', - data={ - 'device_type': device_type, - 'device_id': device_id - } - ) - - get_open_id = get_user_id - - def get_binded_devices(self, user_id): - """ - 通过openid获取用户在当前devicetype下绑定的deviceid列表 - 详情请参考 - http://iot.weixin.qq.com/document-2_13.html - - :param user_id: 要查询的用户的openid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'get_bind_device', - data={'openid': user_id} - ) - - get_bind_device = get_binded_devices - - def send_status_message(self, device_type, device_id, user_id, status): - """ - 主动发送设备状态消息给微信终端 - 详情请参考 - http://iot.weixin.qq.com/document-2_10.html - - :param device_type: 设备类型,目前为“公众账号原始ID” - :param device_id: 设备ID - :param user_id: 微信用户账号的openid - :param status: 设备状态:0--未连接, 1--已连接 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'transmsg', - data={ - 'device_type': device_type, - 'device_id': device_id, - 'open_id': user_id, - 'device_status': status - } - ) - - def authorize(self, devices, op_type=0): - """ - 设备授权 - 详情请参考 - http://iot.weixin.qq.com/document-2_6.html - - :param devices: 设备信息的列表 - :param op_type: 请求操作的类型,限定取值为:0:设备授权 1:设备更新 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'authorize', - data={ - 'device_num': len(devices), - 'device_list': devices, - 'op_type': op_type - } - ) - - def get_qrcode(self): - """ - 获取deviceid和二维码 - 详情请参考 - http://iot.weixin.qq.com/document-2_11.html - - :return: 返回的 JSON 数据包 - """ - return self._get('getqrcode') - - def authorize_device(self, devices, op_type=1): - """ - 设备授权 - 详情请参考 - http://iot.weixin.qq.com/document-2_6.html - - :param devices: 设备信息的列表 - :param op_type: 请求操作的类型,限定取值为:0:设备授权 1:设备更新 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'authorize_device', - data={ - 'device_num': len(devices), - 'device_list': devices, - 'op_type': op_type - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/group.py b/sg_wechat_enterprise/we_api/client/api/group.py deleted file mode 100644 index 90600916..00000000 --- a/sg_wechat_enterprise/we_api/client/api/group.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.utils import to_text -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatGroup(BaseWeChatAPI): - - def create(self, name): - """ - 创建分组 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param name: 分组名字(30个字符以内) - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.group.create('New Group') - - """ - name = to_text(name) - return self._post( - 'groups/create', - data={'group': {'name': name}} - ) - - def get(self, user_id=None): - """ - 查询所有分组或查询用户所在分组 ID - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param user_id: 用户 ID,提供时查询该用户所在分组,否则查询所有分组 - :return: 所有分组列表或用户所在分组 ID - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - group = client.group.get('openid') - - """ - if user_id is None: - res = self._get( - 'groups/get', - result_processor=lambda x: x['groups'] - ) - else: - res = self._post( - 'groups/getid', - data={'openid': user_id}, - result_processor=lambda x: x['groupid'] - ) - return res - - def update(self, group_id, name): - """ - 修改分组名 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param group_id: 分组id,由微信分配 - :param name: 分组名字(30个字符以内) - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.group.update(1234, 'New Name') - - """ - name = to_text(name) - return self._post( - 'groups/update', - data={ - 'group': { - 'id': int(group_id), - 'name': name - } - } - ) - - def move_user(self, user_id, group_id): - """ - 移动用户分组 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param user_id: 用户 ID, 可以是单个或者列表,为列表时为批量移动用户分组 - :param group_id: 分组 ID - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.group.move_user('openid', 1234) - - """ - data = {'to_groupid': group_id} - if isinstance(user_id, (tuple, list)): - endpoint = 'groups/members/batchupdate' - data['openid_list'] = user_id - else: - endpoint = 'groups/members/update' - data['openid'] = user_id - return self._post(endpoint, data=data) - - def delete(self, group_id): - """ - 删除分组 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param group_id: 分组 ID - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.group.delete(1234) - - """ - return self._post( - 'groups/delete', - data={ - 'group': { - 'id': group_id - } - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/jsapi.py b/sg_wechat_enterprise/we_api/client/api/jsapi.py deleted file mode 100644 index 9c444176..00000000 --- a/sg_wechat_enterprise/we_api/client/api/jsapi.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.client.jsapi - ~~~~~~~~~~~~~~~~~~~~ - - This module provides some APIs for JS SDK - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import time - -from wechatpy.utils import WeChatSigner -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatJSAPI(BaseWeChatAPI): - - def get_ticket(self, type='jsapi'): - """ - 获取微信 JS-SDK ticket - - :return: 返回的 JSON 数据包 - """ - return self._get( - 'ticket/getticket', - params={'type': type} - ) - - def get_jsapi_ticket(self): - """ - 获取微信 JS-SDK ticket - - 该方法会通过 session 对象自动缓存管理 ticket - - :return: ticket - """ - ticket = self.session.get('jsapi_ticket') - expires_at = self.session.get('jsapi_ticket_expires_at', 0) - if not ticket or expires_at < int(time.time()): - jsapi_ticket = self.get_ticket('jsapi') - ticket = jsapi_ticket['ticket'] - expires_at = int(time.time()) + int(jsapi_ticket['expires_in']) - self.session.set('jsapi_ticket', ticket) - self.session.set('jsapi_ticket_expires_at', expires_at) - return ticket - - def get_jsapi_signature(self, noncestr, ticket, timestamp, url): - data = [ - 'noncestr={noncestr}'.format(noncestr=noncestr), - 'jsapi_ticket={ticket}'.format(ticket=ticket), - 'timestamp={timestamp}'.format(timestamp=timestamp), - 'url={url}'.format(url=url), - ] - signer = WeChatSigner(delimiter=b'&') - signer.add_data(*data) - return signer.signature diff --git a/sg_wechat_enterprise/we_api/client/api/material.py b/sg_wechat_enterprise/we_api/client/api/material.py deleted file mode 100644 index cf40fde0..00000000 --- a/sg_wechat_enterprise/we_api/client/api/material.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.utils import json -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMaterial(BaseWeChatAPI): - - def add_articles(self, articles): - """ - 新增永久图文素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html - - :param articles: 图文素材数组 - :return: 返回的 JSON 数据包 - """ - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'title': article['title'], - 'content': article['content'], - 'author': article.get('author', ''), - 'content_source_url': article.get('content_source_url', ''), - 'digest': article.get('digest', ''), - 'show_cover_pic': article.get('show_cover_pic', '0') - }) - return self._post( - 'material/add_news', - data={ - 'articles': articles_data - } - ) - - def add(self, media_type, media_file, title=None, introduction=None): - """ - 新增其它类型永久素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html - - :param media_type: 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) - :param media_file: 要上传的文件,一个 File-object - :param title: 视频素材标题,仅上传视频素材时需要 - :param introduction: 视频素材简介,仅上传视频素材时需要 - :return: 返回的 JSON 数据包 - """ - params = { - 'access_token': self.access_token, - 'type': media_type - } - if media_type == 'video': - assert title, 'Video title must be set' - assert introduction, 'Video introduction must be set' - description = { - 'title': title, - 'introduction': introduction - } - params['description'] = json.dumps(description) - return self._post( - 'material/add_material', - params=params, - files={ - 'media': media_file - } - ) - - def get(self, media_id): - """ - 获取永久素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html - - :param media_id: 素材的 media_id - :return: 图文素材返回图文列表,其它类型为素材的内容 - """ - def _processor(res): - if isinstance(res, dict): - # 图文素材 - return res.get('news_item', []) - return res - - res = self._post( - 'material/get_material', - data={ - 'media_id': media_id - }, - result_processor=_processor - ) - return res - - def delete(self, media_id): - """ - 删除永久素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/e66f61c303db51a6c0f90f46b15af5f5.html - - :param media_id: 素材的 media_id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'material/del_material', - data={ - 'media_id': media_id - } - ) - - def update_articles(self, media_id, index, articles): - """ - 修改永久图文素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/4/19a59cba020d506e767360ca1be29450.html - - :param media_id: 要修改的图文消息的 id - :param index: 要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为 0 - :param articles: 图文素材数组 - :return: 返回的 JSON 数据包 - """ - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'title': article['title'], - 'content': article['content'], - 'author': article.get('author', ''), - 'content_source_url': article.get('content_source_url', ''), - 'digest': article.get('digest', ''), - 'show_cover_pic': article.get('show_cover_pic', '0') - }) - return self._post( - 'material/update_news', - data={ - 'media_id': media_id, - 'index': index, - 'articles': articles_data - } - ) - - def batchget(self, media_type, offset=0, count=20): - """ - 批量获取永久素材列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html - - :param media_type: 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(news) - :param offset: 从全部素材的该偏移位置开始返回,0 表示从第一个素材返回 - :param count: 返回素材的数量,取值在1到20之间 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'material/batchget_material', - data={ - 'type': media_type, - 'offset': offset, - 'count': count - } - ) - - def get_count(self): - """ - 获取素材总数 - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8cc64f8c189674b421bee3ed403993b8.html - - :return: 返回的 JSON 数据包 - """ - return self._get('material/get_materialcount') diff --git a/sg_wechat_enterprise/we_api/client/api/media.py b/sg_wechat_enterprise/we_api/client/api/media.py deleted file mode 100644 index e04317b2..00000000 --- a/sg_wechat_enterprise/we_api/client/api/media.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMedia(BaseWeChatAPI): - - def upload(self, media_type, media_file): - """ - 上传临时素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/963fc70b80dc75483a271298a76a8d59.html - - :param media_type: 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) - :param media_file: 要上传的文件,一个 File-object - - :return: 返回的 JSON 数据包 - """ - return self._post( - url='http://file.api.weixin.qq.com/cgi-bin/media/upload', - params={ - 'type': media_type - }, - files={ - 'media': media_file - } - ) - - def download(self, media_id): - """ - 获取临时素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/10/78b15308b053286e2a66b33f0f0f5fb6.html - - :param media_id: 媒体文件 ID - - :return: requests 的 Response 实例 - """ - return self._get( - 'http://file.api.weixin.qq.com/cgi-bin/media/get', - params={ - 'media_id': media_id - } - ) - - def get_url(self, media_id): - """ - 获取临时素材下载地址 - - :param media_id: 媒体文件 ID - :return: 临时素材下载地址 - """ - parts = ( - 'http://file.api.weixin.qq.com/cgi-bin/media/get', - '?access_token=', - self.access_token, - '&media_id=', - media_id - ) - return ''.join(parts) - - def upload_video(self, media_id, title, description): - """ - 群发视频消息时获取视频 media_id - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param media_id: 需通过基础支持中的上传下载多媒体文件 :func:`upload` 来得到 - :param title: 视频标题 - :param description: 视频描述 - - :return: 返回的 JSON 数据包 - """ - return self._post( - url='https://file.api.weixin.qq.com/cgi-bin/media/uploadvideo', - data={ - 'media_id': media_id, - 'title': title, - 'description': description - } - ) - - def upload_articles(self, articles): - """ - 上传图文消息素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param articles: 图文消息数组 - :return: 返回的 JSON 数据包 - """ - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'title': article['title'], - 'content': article['content'], - 'author': article.get('author', ''), - 'content_source_url': article.get('content_source_url', ''), - 'digest': article.get('digest', ''), - 'show_cover_pic': article.get('show_cover_pic', '0') - }) - return self._post( - 'media/uploadnews', - data={ - 'articles': articles_data - } - ) - - def upload_mass_image(self, media_file): - """ - 上传群发消息内的图片 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param media_file: 要上传的文件,一个 File-object - :return: 上传成功时返回图片 URL - """ - res = self._post( - url='https://api.weixin.qq.com/cgi-bin/media/uploadimg', - files={ - 'media': media_file - }, - result_processor=lambda x: x['url'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/menu.py b/sg_wechat_enterprise/we_api/client/api/menu.py deleted file mode 100644 index e355e8ad..00000000 --- a/sg_wechat_enterprise/we_api/client/api/menu.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.exceptions import WeChatClientException -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMenu(BaseWeChatAPI): - - def get(self): - """ - 查询自定义菜单。 - 详情请参考 - http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - menu = client.menu.get() - - """ - try: - return self._get('menu/get') - except WeChatClientException as e: - if e.errcode == 46003: - # menu not exist - return None - else: - raise e - - def create(self, menu_data): - """ - 创建自定义菜单 :: - - from wechatpy import WeChatClient - - client = WeChatClient("appid", "secret") - client.menu.create({ - "button":[ - { - "type":"click", - "name":"今日歌曲", - "key":"V1001_TODAY_MUSIC" - }, - { - "type":"click", - "name":"歌手简介", - "key":"V1001_TODAY_SINGER" - }, - { - "name":"菜单", - "sub_button":[ - { - "type":"xml", - "name":"搜索", - "url":"http://www.soso.com/" - }, - { - "type":"xml", - "name":"视频", - "url":"http://v.qq.com/" - }, - { - "type":"click", - "name":"赞一下我们", - "key":"V1001_GOOD" - } - ] - } - ] - }) - - 详情请参考 - https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013 - - :param menu_data: Python 字典 - - :return: 返回的 JSON 数据包 - """ - return self._post( - 'menu/create', - data=menu_data - ) - - update = create - - def delete(self): - """ - 删除自定义菜单。 - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8ed41ba931e4845844ad6d1eeb8060c8.html - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.menu.delete() - - """ - return self._get('menu/delete') - - def get_menu_info(self): - """ - 获取自定义菜单配置 - 详情请参考 - http://mp.weixin.qq.com/wiki/17/4dc4b0514fdad7a5fbbd477aa9aab5ed.html - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - menu_info = client.menu.get_menu_info() - - """ - return self._get('get_current_selfmenu_info') - - def add_conditional(self, menu_data): - """ - 创建个性化菜单 :: - - from wechatpy import WeChatClient - - client = WeChatClient("appid", "secret") - client.menu.add_conditional({ - "button":[ - { - "type":"click", - "name":"今日歌曲", - "key":"V1001_TODAY_MUSIC" - }, - { - "type":"click", - "name":"歌手简介", - "key":"V1001_TODAY_SINGER" - }, - { - "name":"菜单", - "sub_button":[ - { - "type":"xml", - "name":"搜索", - "url":"http://www.soso.com/" - }, - { - "type":"xml", - "name":"视频", - "url":"http://v.qq.com/" - }, - { - "type":"click", - "name":"赞一下我们", - "key":"V1001_GOOD" - } - ] - } - ], - "matchrule":{ - "group_id":"2", - "sex":"1", - "country":"中国", - "province":"广东", - "city":"广州", - "client_platform_type":"2" - } - }) - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html - - :param menu_data: Python 字典 - - :return: 返回的 JSON 数据包 - """ - return self._post( - 'menu/addconditional', - data=menu_data - ) - - def del_conditional(self, menu_id): - """ - 删除个性化菜单 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html - - :param menu_id: 菜单ID - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.menu.del_conditional('menu_id') - - """ - return self._post( - 'menu/delconditional', - data={'menuid': menu_id} - ) - - def try_match(self, user_id): - """ - 测试个性化菜单匹配结果 - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/c48ccd12b69ae023159b4bfaa7c39c20.html - - :param user_id: 可以是粉丝的OpenID,也可以是粉丝的微信号。 - - :return: 该接口将返回菜单配置 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.menu.try_match('openid') - - """ - return self._post( - 'menu/trymatch', - data={'user_id': user_id} - ) diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/__init__.py b/sg_wechat_enterprise/we_api/client/api/merchant/__init__.py deleted file mode 100644 index 93899db7..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - -from wechatpy.client.api.merchant.category import MerchantCategory -from wechatpy.client.api.merchant.stock import MerchantStock -from wechatpy.client.api.merchant.express import MerchantExpress -from wechatpy.client.api.merchant.group import MerchantGroup -from wechatpy.client.api.merchant.shelf import MerchantShelf -from wechatpy.client.api.merchant.order import MerchantOrder -from wechatpy.client.api.merchant.common import MerchantCommon - - -class WeChatMerchant(BaseWeChatAPI): - - def __init__(self, *args, **kwargs): - super(WeChatMerchant, self).__init__(*args, **kwargs) - - # sub APIs - self.category = MerchantCategory(self._client) - self.stock = MerchantStock(self._client) - self.express = MerchantExpress(self._client) - self.group = MerchantGroup(self._client) - self.shelf = MerchantShelf(self._client) - self.order = MerchantOrder(self._client) - self.common = MerchantCommon(self._client) - - def create(self, product_data): - return self._post( - 'merchant/create', - data=product_data - ) - - def delete(self, product_id): - return self._post( - 'merchant/del', - data={ - 'product_id': product_id - } - ) - - def update(self, product_id, product_data): - product_data['product_id'] = product_id - return self._post( - 'merchant/update', - data=product_data - ) - - def get(self, product_id): - return self._post( - 'merchant/get', - data={ - 'product_id': product_id - } - ) - - def get_by_status(self, status): - return self._post( - 'merchant/getbystatus', - data={ - 'status': status - } - ) - - def update_product_status(self, product_id, status): - return self._post( - 'merchant/modproductstatus', - data={ - 'product_id': product_id, - 'status': status - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/category.py b/sg_wechat_enterprise/we_api/client/api/merchant/category.py deleted file mode 100644 index b26377ed..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/category.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantCategory(BaseWeChatAPI): - - def get_sub_categories(self, cate_id): - res = self._post( - 'merchant/category/getsub', - data={'cate_id': cate_id}, - result_processor=lambda x: x['cate_list'] - ) - return res - - def get_sku_list(self, cate_id): - res = self._post( - 'merchant/category/getsku', - data={'cate_id': cate_id}, - result_processor=lambda x: x['sku_table'] - ) - return res - - def get_properties(self, cate_id): - res = self._post( - 'merchant/category/getproperty', - data={'cate_id': cate_id}, - result_processor=lambda x: x['properties'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/common.py b/sg_wechat_enterprise/we_api/client/api/merchant/common.py deleted file mode 100644 index 9730a311..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/common.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantCommon(BaseWeChatAPI): - - def upload_image(self, filename, image_data): - res = self._post( - 'merchant/common/upload_img', - params={ - 'filename': filename - }, - data=image_data, - result_processor=lambda x: x['image_url'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/express.py b/sg_wechat_enterprise/we_api/client/api/merchant/express.py deleted file mode 100644 index 27fe2b22..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/express.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantExpress(BaseWeChatAPI): - - def add(self, delivery_template): - return self._post( - 'merchant/express/add', - data={ - 'delivery_template': delivery_template - } - ) - - def delete(self, template_id): - return self._post( - 'merchant/express/del', - data={ - 'template_id': template_id - } - ) - - def update(self, template_id, delivery_template): - return self._post( - 'merchant/express/update', - data={ - 'template_id': template_id, - 'delivery_template': delivery_template - } - ) - - def get(self, template_id): - res = self._post( - 'merchant/express/getbyid', - data={ - 'template_id': template_id - }, - result_processor=lambda x: x['template_info'] - ) - return res - - def get_all(self): - res = self._get( - 'merchant/express/getall', - result_processor=lambda x: x['template_info'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/group.py b/sg_wechat_enterprise/we_api/client/api/merchant/group.py deleted file mode 100644 index 593b3751..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/group.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantGroup(BaseWeChatAPI): - - def add(self, name, product_list): - return self._post( - 'merchant/group/add', - data={ - 'group_detail': { - 'group_name': name, - 'product_list': product_list - } - } - ) - - def delete(self, group_id): - return self._post( - 'merchant/group/del', - data={ - 'group_id': group_id - } - ) - - def update(self, group_id, name): - return self._post( - 'merchant/group/propertymod', - data={ - 'group_id': group_id, - 'group_name': name - } - ) - - def update_product(self, group_id, product): - return self._post( - 'merchant/group/productmod', - data={ - 'group_id': group_id, - 'product': product - } - ) - - def get_all(self): - res = self._get( - 'merchant/group/getall', - result_processor=lambda x: x['group_detail'] - ) - return res - - def get(self, group_id): - res = self._post( - 'merchant/group/getbyid', - data={ - 'group_id': group_id - }, - result_processor=lambda x: x['group_detail'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/order.py b/sg_wechat_enterprise/we_api/client/api/merchant/order.py deleted file mode 100644 index 09d4b503..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/order.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantOrder(BaseWeChatAPI): - - def get(self, order_id): - res = self._post( - 'merchant/order/getbyid', - data={ - 'order_id': order_id - }, - result_processor=lambda x: x['order'] - ) - return res - - def get_by_filter(self, status=None, begin_time=None, end_time=None): - filter_dict = optionaldict( - status=status, - begintime=begin_time, - endtime=end_time - ) - - res = self._post( - 'merchant/order/getbyfilter', - data=dict(filter_dict), - result_processor=lambda x: x['order_list'] - ) - return res - - def set_delivery(self, order_id, company, track_no, - need_delivery=1, is_others=0): - return self._post( - 'merchant/order/setdelivery', - data={ - 'order_id': order_id, - 'delivery_company': company, - 'delivery_track_no': track_no, - 'need_delivery': need_delivery, - 'is_others': is_others - } - ) - - def close(self, order_id): - return self._post( - 'merchant/order/close', - data={ - 'order_id': order_id - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/shelf.py b/sg_wechat_enterprise/we_api/client/api/merchant/shelf.py deleted file mode 100644 index 79354951..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/shelf.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantShelf(BaseWeChatAPI): - - def add(self, name, banner, shelf_data): - return self._post( - 'merchant/shelf/add', - data={ - 'shelf_name': name, - 'shelf_banner': banner, - 'shelf_data': shelf_data - } - ) - - def delete(self, shelf_id): - return self._post( - 'merchant/shelf/del', - data={ - 'shelf_id': shelf_id - } - ) - - def update(self, shelf_id, name, banner, shelf_data): - return self._post( - 'merchant/shelf/add', - data={ - 'shelf_id': shelf_id, - 'shelf_name': name, - 'shelf_banner': banner, - 'shelf_data': shelf_data - } - ) - - def get_all(self): - res = self._get( - 'merchant/shelf/getall', - result_processor=lambda x: x['shelves'] - ) - return res - - def get(self, shelf_id): - return self._post( - 'merchant/shelf/getbyid', - data={ - 'shelf_id': shelf_id - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/merchant/stock.py b/sg_wechat_enterprise/we_api/client/api/merchant/stock.py deleted file mode 100644 index bfe09de6..00000000 --- a/sg_wechat_enterprise/we_api/client/api/merchant/stock.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class MerchantStock(BaseWeChatAPI): - - def add(self, product_id, quantity, sku_info=''): - return self._post( - 'merchant/stock/add', - data={ - 'product_id': product_id, - 'quantity': quantity, - 'sku_info': sku_info - } - ) - - def reduce(self, product_id, quantity, sku_info=''): - return self._post( - 'merchant/stock/reduce', - data={ - 'product_id': product_id, - 'quantity': quantity, - 'sku_info': sku_info - } - ) diff --git a/sg_wechat_enterprise/we_api/client/api/message.py b/sg_wechat_enterprise/we_api/client/api/message.py deleted file mode 100644 index e1fb3e44..00000000 --- a/sg_wechat_enterprise/we_api/client/api/message.py +++ /dev/null @@ -1,572 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import re -import six - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMessage(BaseWeChatAPI): - OPENID_RE = re.compile(r'^[\w\-]{28}$', re.I) - - def _send_custom_message(self, data, account=None): - data = data or {} - if account: - data['customservice'] = {'kf_account': account} - return self._post( - 'message/custom/send', - data=data - ) - - def send_text(self, user_id, content, account=None): - """ - 发送文本消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param content: 消息正文 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.send_text('openid', 'text') - - """ - data = { - 'touser': user_id, - 'msgtype': 'text', - 'text': {'content': content} - } - return self._send_custom_message(data, account=account) - - def send_image(self, user_id, media_id, account=None): - """ - 发送图片消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param media_id: 图片的媒体ID。 可以通过 :func:`upload_media` 上传。 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.send_image('openid', 'media_id') - - """ - data = { - 'touser': user_id, - 'msgtype': 'image', - 'image': { - 'media_id': media_id - } - } - return self._send_custom_message(data, account=account) - - def send_voice(self, user_id, media_id, account=None): - """ - 发送语音消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param media_id: 发送的语音的媒体ID。 可以通过 :func:`upload_media` 上传。 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.send_voice('openid', 'media_id') - - """ - data = { - 'touser': user_id, - 'msgtype': 'voice', - 'voice': { - 'media_id': media_id - } - } - return self._send_custom_message(data, account=account) - - def send_video(self, user_id, media_id, title=None, - description=None, account=None): - """ - 发送视频消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param media_id: 发送的视频的媒体ID。 可以通过 :func:`upload_media` 上传。 - :param title: 视频消息的标题 - :param description: 视频消息的描述 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.send_video('openid', 'media_id', 'title', 'description') - """ - video_data = { - 'media_id': media_id, - } - if title: - video_data['title'] = title - if description: - video_data['description'] = description - - data = { - 'touser': user_id, - 'msgtype': 'video', - 'video': video_data - } - return self._send_custom_message(data, account=account) - - def send_music(self, user_id, url, hq_url, thumb_media_id, - title=None, description=None, account=None): - """ - 发送音乐消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param url: 音乐链接 - :param hq_url: 高品质音乐链接,wifi环境优先使用该链接播放音乐 - :param thumb_media_id: 缩略图的媒体ID。 可以通过 :func:`upload_media` 上传。 - :param title: 音乐标题 - :param description: 音乐描述 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - """ - music_data = { - 'musicurl': url, - 'hqmusicurl': hq_url, - 'thumb_media_id': thumb_media_id - } - if title: - music_data['title'] = title - if description: - music_data['description'] = description - - data = { - 'touser': user_id, - 'msgtype': 'music', - 'music': music_data - } - return self._send_custom_message(data, account=account) - - def send_articles(self, user_id, articles, account=None): - """ - 发送图文消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param articles: 一个包含至多10个图文的数组, 或者微信图文消息素材 media_id - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - """ - if isinstance(articles, (tuple, list)): - articles_data = [] - for article in articles: - articles_data.append({ - 'title': article['title'], - 'description': article['description'], - 'url': article['url'], - 'picurl': article.get('image', article.get('picurl')), - }) - data = { - 'touser': user_id, - 'msgtype': 'news', - 'news': { - 'articles': articles_data - } - } - else: - data = { - 'touser': user_id, - 'msgtype': 'mpnews', - 'mpnews': { - 'media_id': articles, - } - } - return self._send_custom_message(data, account=account) - - def send_card(self, user_id, card_id, card_ext, account=None): - """ - 发送卡券消息 - - 详情请参参考 - http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param card_id: 卡券 ID - :param card_ext: 卡券扩展信息 - :param account: 可选,客服账号 - :return: 返回的 JSON 数据包 - """ - data = { - 'touser': user_id, - 'msgtype': 'wxcard', - 'wxcard': { - 'card_id': card_id, - 'card_ext': card_ext - } - } - return self._send_custom_message(data, account=account) - - def delete_mass(self, msg_id): - """ - 删除群发消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param msg_id: 要删除的群发消息 ID - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.delete_mass('message id') - - """ - return self._post( - 'message/mass/delete', - data={ - 'msg_id': msg_id - } - ) - - def _send_mass_message(self, group_or_users, msg_type, msg, - is_to_all=False, preview=False): - data = { - 'msgtype': msg_type - } - if not preview: - if isinstance(group_or_users, (tuple, list)): - # send by user ids - data['touser'] = group_or_users - endpoint = 'message/mass/send' - else: - # send by group id - data['filter'] = { - 'group_id': group_or_users, - 'is_to_all': is_to_all, - } - endpoint = 'message/mass/sendall' - else: - if not isinstance(group_or_users, six.string_types): - raise ValueError('group_or_users should be string types') - # 预览接口 - if self.OPENID_RE.match(group_or_users): - # 按照 openid 预览群发 - data['touser'] = group_or_users - else: - # 按照微信号预览群发 - data['towxname'] = group_or_users - endpoint = 'message/mass/preview' - - data.update(msg) - return self._post( - endpoint, - data=data - ) - - def send_mass_text(self, group_or_users, content, - is_to_all=False, preview=False): - """ - 群发文本消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param content: 消息正文 - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - return self._send_mass_message( - group_or_users, - 'text', - { - 'text': { - 'content': content - } - }, - is_to_all, - preview - ) - - def send_mass_image(self, group_or_users, media_id, - is_to_all=False, preview=False): - """ - 群发图片消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param media_id: 图片的媒体 ID。 可以通过 :func:`upload_media` 上传。 - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - return self._send_mass_message( - group_or_users, - 'image', - { - 'image': { - 'media_id': media_id - } - }, - is_to_all, - preview - ) - - def send_mass_voice(self, group_or_users, media_id, - is_to_all=False, preview=False): - """ - 群发语音消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param media_id: 语音的媒体 ID。可以通过 :func:`upload_media` 上传。 - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - return self._send_mass_message( - group_or_users, - 'voice', - { - 'voice': { - 'media_id': media_id - } - }, - is_to_all, - preview - ) - - def send_mass_video(self, group_or_users, media_id, title=None, - description=None, is_to_all=False, preview=False): - """ - 群发视频消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param media_id: 视频的媒体 ID。可以通过 :func:`upload_video` 上传。 - :param title: 视频标题 - :param description: 视频描述 - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - video_data = { - 'media_id': media_id - } - if title: - video_data['title'] = title - if description: - video_data['description'] = description - return self._send_mass_message( - group_or_users, - 'mpvideo', - { - 'mpvideo': video_data - }, - is_to_all, - preview - ) - - def send_mass_article(self, group_or_users, media_id, - is_to_all=False, preview=False): - """ - 群发图文消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param media_id: 图文的媒体 ID。可以通过 :func:`upload_articles` 上传。 - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - return self._send_mass_message( - group_or_users, - 'mpnews', - { - 'mpnews': { - 'media_id': media_id - } - }, - is_to_all, - preview - ) - - def get_mass(self, msg_id): - """ - 查询群发消息发送状态 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param msg_id: 群发消息后返回的消息id - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.message.get_mass('mass message id') - - """ - return self._post( - 'message/mass/get', - data={ - 'msg_id': msg_id - } - ) - - def send_template(self, user_id, template_id, url, top_color, data): - """ - 发送模板消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param template_id: 模板 ID。在公众平台线上模板库中选用模板获得 - :param url: 链接地址 - :param top_color: 消息顶部颜色 - :param data: 模板消息数据 - - :return: 返回的 JSON 数据包 - """ - return self._post( - 'message/template/send', - data={ - 'touser': user_id, - 'template_id': template_id, - 'url': url, - 'topcolor': top_color, - 'data': data - } - ) - - def send_template_applet(self, user_id, template_id, appid, pagepath, data): - """ - 发送模板消息,点击跳转到小程序 - - 详情请参考 - https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277 - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param template_id: 模板 ID。在公众平台线上模板库中选用模板获得 - :param appid: 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏) - :param pagepath: 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏 不必填 - :param data: 模板消息数据 - - :return: 返回的 JSON 数据包 - """ - data_all = { - 'touser': user_id, - 'template_id': template_id, - "miniprogram": { - "appid": appid, - # "pagepath": pagepath - }, - 'data': data - } - if pagepath: - data_all['miniprogram']['pagepath'] = pagepath - return self._post( - 'message/template/send', - data=data_all - ) - - def get_autoreply_info(self): - """ - 获取自动回复规则 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/7b5789bb1262fb866d01b4b40b0efecb.html - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - info = client.message.get_autoreply_info() - - """ - return self._get('get_current_autoreply_info') - - def send_mass_card(self, group_or_users, card_id, - is_to_all=False, preview=False): - """ - 群发卡券消息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - - :param group_or_users: 值为整型数字时为按分组群发,值为列表/元组时为按 OpenID 列表群发 - :param card_id: 卡券 ID - :param is_to_all: 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户 - 选择false可根据group_id发送给指定群组的用户 - :param preview: 是否发送预览,此时 group_or_users 参数应为一个openid字符串 - - :return: 返回的 JSON 数据包 - """ - return self._send_mass_message( - group_or_users, - 'wxcard', - { - 'wxcard': { - 'card_id': card_id - } - }, - is_to_all, - preview - ) diff --git a/sg_wechat_enterprise/we_api/client/api/misc.py b/sg_wechat_enterprise/we_api/client/api/misc.py deleted file mode 100644 index 75623e10..00000000 --- a/sg_wechat_enterprise/we_api/client/api/misc.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMisc(BaseWeChatAPI): - - def short_url(self, long_url): - """ - 将一条长链接转成短链接 - 详情请参考 - http://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html - - :param long_url: 长链接地址 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.misc.short_url('http://www.qq.com') - - """ - return self._post( - 'shorturl', - data={ - 'action': 'long2short', - 'long_url': long_url - } - ) - - def get_wechat_ips(self): - """ - 获取微信服务器 IP 地址列表 - - :return: IP 地址列表 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - ips = client.misc.get_wechat_ips() - - """ - res = self._get( - 'getcallbackip', - result_processor=lambda x: x['ip_list'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/poi.py b/sg_wechat_enterprise/we_api/client/api/poi.py deleted file mode 100644 index a25ffc02..00000000 --- a/sg_wechat_enterprise/we_api/client/api/poi.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatPoi(BaseWeChatAPI): - - def add_picture(self, access_token, buffer): - """ - 上传图片接口 - :param access_token: 接口凭证 - :param buffer: buffer - :return: - 详情请参考: https://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - """ - - def add(self, poi_data): - """ - 创建门店 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :param poi_data: 门店信息字典 - :return: 返回的 JSON 数据包 - """ - return self._post('poi/addpoi', data=poi_data) - - def get(self, poi_id): - """ - 查询门店信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :param poi_id: 门店 ID - :return: 返回的 JSON 数据包 - """ - return self._post('poi/getpoi', data={'poi_id': poi_id}) - - def list(self, begin=0, limit=20): - """ - 查询门店列表 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :param begin: 开始位置,0 即为从第一条开始查询 - :param limit: 返回数据条数,最大允许50,默认为20 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'poi/getpoilist', - data={ - 'begin': begin, - 'limit': limit, - } - ) - - def update(self, poi_data): - """ - 修改门店 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :param poi_data: 门店信息字典 - :return: 返回的 JSON 数据包 - """ - return self._post('poi/updatepoi', data=poi_data) - - def delete(self, poi_id): - """ - 删除门店 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :param poi_id: 门店 ID - :return: 返回的 JSON 数据包 - """ - return self._post('poi/delpoi', data={'poi_id': poi_id}) - - def get_categories(self): - """ - 获取微信门店类目表 - - 详情请参考 - http://mp.weixin.qq.com/wiki/16/8f182af4d8dcea02c56506306bdb2f4c.html - - :return: 门店类目表 - """ - res = self._get( - 'api_getwxcategory', - result_processor=lambda x: x['category_list'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/qrcode.py b/sg_wechat_enterprise/we_api/client/api/qrcode.py deleted file mode 100644 index 3edb7894..00000000 --- a/sg_wechat_enterprise/we_api/client/api/qrcode.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import requests -import six - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatQRCode(BaseWeChatAPI): - - def create(self, qrcode_data): - """ - 创建二维码 - 详情请参考 - http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html - - :param data: 你要发送的参数 dict - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.qrcode.create({ - 'expire_seconds': 1800, - 'action_name': 'QR_SCENE', - 'action_info': { - 'scene': {'scene_id': 123}, - } - }) - - """ - return self._post( - 'qrcode/create', - data=qrcode_data - ) - - def show(self, ticket): - """ - 通过ticket换取二维码 - 详情请参考 - http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html - - :param ticket: 二维码 ticket 。可以通过 :func:`create` 获取到 - :return: 返回的 Request 对象 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.qrcode.show('ticket data') - - """ - if isinstance(ticket, dict): - ticket = ticket['ticket'] - return requests.get( - url='https://mp.weixin.qq.com/cgi-bin/showqrcode', - params={ - 'ticket': ticket - } - ) - - @classmethod - def get_url(cls, ticket): - """ - 通过ticket换取二维码地址 - 详情请参考 - http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html - - :param ticket: 二维码 ticket 。可以通过 :func:`create` 获取到 - :return: 返回的二维码地址 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - url = client.qrcode.get_url('ticket data') - - """ - url = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={ticket}' - if isinstance(ticket, dict): - ticket = ticket['ticket'] - ticket = six.moves.urllib.parse.quote(ticket) - return url.format(ticket=ticket) diff --git a/sg_wechat_enterprise/we_api/client/api/scan.py b/sg_wechat_enterprise/we_api/client/api/scan.py deleted file mode 100644 index 6bbf80f6..00000000 --- a/sg_wechat_enterprise/we_api/client/api/scan.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatScan(BaseWeChatAPI): - API_BASE_URL = 'https://api.weixin.qq.com/scan/' - - def get_merchant_info(self): - """ - 获取商户信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/6/c61604ff6890d386d6227945ad4a68d2.html - - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - info = client.scan.get_merchant_info() - """ - return self._get('merchantinfo/get') - - def create_product(self, product_data): - """ - 创建商品 - - 详情请参考 - http://mp.weixin.qq.com/wiki/6/c61604ff6890d386d6227945ad4a68d2.html - - :return: 返回的 JSON 数据包 - """ - return self._post('product/create', data=product_data) - - def modify_product_status(self, standard, key, status): - """ - 提交审核/取消发布商品 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/1007691d0f1c10a0588c6517f12ed70f.html - - :param standard: 商品编码标准 - :param key: 商品编码内容 - :param status: 设置发布状态。on 为提交审核,off 为取消发布 - :return: 返回的 JSON 数据包 - """ - data = { - 'keystandard': standard, - 'keystr': key, - 'status': status, - } - return self._post('product/modstatus', data=data) - - def publish_product(self, standard, key): - """ - 提交审核商品 shortcut 接口 - - 等同于调用 ``modify_product_status(standard, key, 'on')`` - """ - return self.modify_product_status(standard, key, 'on') - - def unpublish_product(self, standard, key): - """ - 取消发布商品 shortcut 接口 - - 等同于调用 ``modify_product_status(standard, key, 'off')`` - """ - return self.modify_product_status(standard, key, 'off') - - def set_test_whitelist(self, userids=None, usernames=None): - """ - 设置测试人员白名单 - - 注意:每次设置均被视为一次重置,而非增量设置。openid、微信号合计最多设置10个。 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/1007691d0f1c10a0588c6517f12ed70f.html - - :param userids: 可选,测试人员的 openid 列表 - :param usernames: 可选,测试人员的微信号列表 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - openid=userids, - username=usernames - ) - return self._post('testwhitelist/set', data=data) - - def get_product(self, standard, key): - """ - 查询商品信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/7fa787701295b884410b5163e13313af.html - - :param standard: 商品编码标准 - :param key: 商品编码内容 - :return: 返回的 JSON 数据包 - """ - data = { - 'keystandard': standard, - 'keystr': key, - } - return self._post('product/get', data=data) - - def list_product(self, offset=0, limit=10, status=None, key=None): - """ - 批量查询商品信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/7fa787701295b884410b5163e13313af.html - - :param offset: 可选,批量查询的起始位置,从 0 开始,包含该起始位置 - :param limit: 可选,批量查询的数量,默认为 10 - :param status: 可选,支持按状态拉取。on为发布状态,off为未发布状态, - check为审核中状态,reject为审核未通过状态,all为所有状态 - :param key: 支持按部分编码内容拉取。填写该参数后,可将编码内容中包含所传参数的商品信息拉出 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - offset=offset, - limit=limit, - status=status, - keystr=key, - ) - return self._post('product/getlist', data=data) - - def update_product(self, product_data): - """ - 更新商品信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/7fa787701295b884410b5163e13313af.html - - :return: 返回的 JSON 数据包 - """ - return self._post('product/update', data=product_data) - - def clear_product(self, standard, key): - """ - 清除商品信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/7fa787701295b884410b5163e13313af.html - - :param standard: 商品编码标准 - :param key: 商品编码内容 - :return: 返回的 JSON 数据包 - """ - data = { - 'keystandard': standard, - 'keystr': key, - } - return self._post('product/clear', data=data) - - def check_ticket(self, ticket): - """ - 检查 wxticket 参数有效性 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/7fa787701295b884410b5163e13313af.html - - :param ticket: 请求 URL 中带上的 wxticket 参数 - :return: 返回的 JSON 数据包 - """ - return self._post('scanticket/check', data={'ticket': ticket}) diff --git a/sg_wechat_enterprise/we_api/client/api/semantic.py b/sg_wechat_enterprise/we_api/client/api/semantic.py deleted file mode 100644 index 6dc61252..00000000 --- a/sg_wechat_enterprise/we_api/client/api/semantic.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatSemantic(BaseWeChatAPI): - - def search(self, - query, - category, - uid=None, - latitude=None, - longitude=None, - city=None, - region=None): - """ - 发送语义理解请求 - 详情请参考 - http://mp.weixin.qq.com/wiki/0/0ce78b3c9524811fee34aba3e33f3448.html - - :param query: 输入文本串 - :param category: 需要使用的服务类型,多个可传入列表 - :param uid: 可选,用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid) - :param latitude: 可选,纬度坐标,与经度同时传入;与城市二选一传入 - :param longitude: 可选,经度坐标,与纬度同时传入;与城市二选一传入 - :param city: 可选,城市名称,与经纬度二选一传入 - :param region: 可选,区域名称,在城市存在的情况下可省;与经纬度二选一传入 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - res = client.semantic.search( - '查一下明天从北京到上海的南航机票', - 'flight,hotel', - city='北京' - ) - - """ - if isinstance(category, (tuple, list)): - category = ','.join(category) - - data = optionaldict() - data['query'] = query - data['category'] = category - data['uid'] = uid - data['latitude'] = latitude - data['longitude'] = longitude - data['city'] = city - data['region'] = region - data['appid'] = self._client.appid - return self._post( - url='https://api.weixin.qq.com/semantic/semproxy/search', - data=data - ) diff --git a/sg_wechat_enterprise/we_api/client/api/shakearound.py b/sg_wechat_enterprise/we_api/client/api/shakearound.py deleted file mode 100644 index 6f63dd9a..00000000 --- a/sg_wechat_enterprise/we_api/client/api/shakearound.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import time -from datetime import datetime - -import six -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatShakeAround(BaseWeChatAPI): - - API_BASE_URL = 'https://api.weixin.qq.com/' - - @classmethod - def _to_timestamp(cls, date): - if isinstance(date, six.string_types): - date = datetime.strptime(date, '%Y-%m-%d %H:%M:%S') - if isinstance(date, datetime): - timestamp = int(time.mktime(date.timetuple())) - return timestamp - return int(date) - - def apply_device_id(self, quantity, reason, poi_id=None, comment=None): - """ - 申请设备ID - 详情请参考 - http://mp.weixin.qq.com/wiki/15/b9e012f917e3484b7ed02771156411f3.html - - :param quantity: 申请的设备ID的数量,单次新增设备超过500个,需走人工审核流程 - :param reason: 申请理由,不超过100个字 - :param poi_id: 可选,设备关联的门店ID - :param comment: 可选,备注,不超过15个汉字或30个英文字母 - :return: 申请的设备信息 - """ - data = optionaldict() - data['quantity'] = quantity - data['apply_reason'] = reason - data['poi_id'] = poi_id - data['comment'] = comment - res = self._post( - 'shakearound/device/applyid', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def update_device(self, device_id=None, uuid=None, major=None, - minor=None, comment=None): - """ - 更新设备信息 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/b9e012f917e3484b7ed02771156411f3.html - - :param device_id: 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 - :param uuid: UUID - :param major: major - :param minor: minor - :param comment: 设备的备注信息,不超过15个汉字或30个英文字母。 - :return: 返回的 JSON 数据包 - """ - data = optionaldict() - data['comment'] = comment - data['device_identifier'] = { - 'device_id': device_id, - 'uuid': uuid, - 'major': major, - 'minor': minor - } - return self._post( - 'shakearound/device/update', - data=data - ) - - def bind_device_location(self, poi_id, device_id=None, uuid=None, - major=None, minor=None): - """ - 配置设备与门店的关联关系 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/b9e012f917e3484b7ed02771156411f3.html - - :param poi_id: 待关联的门店ID - :param device_id: 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 - :param uuid: UUID - :param major: major - :param minor: minor - :return: 返回的 JSON 数据包 - """ - data = optionaldict() - data['poi_id'] = poi_id - data['device_identifier'] = { - 'device_id': device_id, - 'uuid': uuid, - 'major': major, - 'minor': minor - } - return self._post( - 'shakearound/device/bindlocation', - data=data - ) - - def search_device(self, identifiers=None, apply_id=None, - begin=0, count=10): - """ - 查询设备列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/b9e012f917e3484b7ed02771156411f3.html - - :param identifiers: 设备 ID 信息列表 - :param apply_id: 批次ID,申请设备ID超出500个时所返回批次ID - :param begin: 设备列表的起始索引值 - :param count: 待查询的设备个数 - :return: 设备列表 - """ - data = optionaldict() - data['begin'] = begin - data['count'] = count - data['apply_id'] = apply_id - if identifiers: - data['device_identifiers'] = identifiers - res = self._post( - 'shakearound/device/search', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def add_page(self, title, description, icon_url, page_url, comment=None): - """ - 新增页面 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/6626199ea8757c752046d8e46cf13251.html - - :param title: 在摇一摇页面展示的主标题,不超过6个字 - :param description: 在摇一摇页面展示的副标题,不超过7个字 - :param icon_url: 在摇一摇页面展示的图片。图片需先上传至微信侧服务器, - 用“素材管理-上传图片素材”接口上传图片,返回的图片URL再配置在此处 - :param page_url: 跳转链接 - :param comment: 可选,页面的备注信息,不超过15个字 - :return: 页面信息 - """ - data = optionaldict() - data['title'] = title - data['description'] = description - data['icon_url'] = icon_url - data['page_url'] = page_url - data['comment'] = comment - res = self._post( - 'shakearound/page/add', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def update_page(self, page_id, title, description, - icon_url, page_url, comment=None): - """ - 编辑页面信息 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/6626199ea8757c752046d8e46cf13251.html - - :param page_id: 摇周边页面唯一ID - :param title: 在摇一摇页面展示的主标题,不超过6个字 - :param description: 在摇一摇页面展示的副标题,不超过7个字 - :param icon_url: 在摇一摇页面展示的图片。图片需先上传至微信侧服务器, - 用“素材管理-上传图片素材”接口上传图片,返回的图片URL再配置在此处 - :param page_url: 跳转链接 - :param comment: 可选,页面的备注信息,不超过15个字 - :return: 页面信息 - """ - data = optionaldict() - data['page_id'] = page_id - data['title'] = title - data['description'] = description - data['icon_url'] = icon_url - data['page_url'] = page_url - data['comment'] = comment - res = self._post( - 'shakearound/page/update', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def search_pages(self, page_ids=None, begin=0, count=10): - """ - 查询页面列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/6626199ea8757c752046d8e46cf13251.html - - :param page_ids: 指定页面的id列表 - :param begin: 页面列表的起始索引值 - :param count: 待查询的页面个数 - :return: 页面查询结果信息 - """ - if not page_ids: - data = { - 'type': 2, - 'begin': begin, - 'count': count - } - else: - if not isinstance(page_ids, (tuple, list)): - page_ids = [page_ids] - data = { - 'type': 1, - 'page_ids': page_ids - } - - res = self._post( - 'shakearound/page/search', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def delete_page(self, page_id): - """ - 删除页面 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/6626199ea8757c752046d8e46cf13251.html - - :param page_id: 指定页面的id列表 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'shakearound/page/delete', - data={ - 'page_id': page_id - } - ) - - def add_material(self, media_file, media_type='icon'): - """ - 上传图片素材 - 详情请参考 - http://mp.weixin.qq.com/wiki/5/e997428269ff189d8f9a4b9e177be2d9.html - - :param media_file: 要上传的文件,一个 File-object - :param media_type: 摇一摇素材类型, 取值为 icon或者 license, 默认 icon. - :return: 上传的素材信息 - """ - res = self._post( - 'shakearound/material/add', - files={ - 'media': media_file - }, - params={ - 'type': media_type - }, - result_processor=lambda x: x['data'] - ) - return res - - def bind_device_pages(self, page_ids, bind, append, device_id=None, - uuid=None, major=None, minor=None): - """ - 配置设备与页面的关联关系 - 详情请参考 - http://mp.weixin.qq.com/wiki/12/c8120214ec0ba08af5dfcc0da1a11400.html - - :param page_ids: 待关联的页面列表 - :param bind: 关联操作标志位, 0为解除关联关系,1为建立关联关系 - :param append: 新增操作标志位, 0为覆盖,1为新增 - :param device_id: 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 - :param uuid: UUID - :param major: major - :param minor: minor - :return: 返回的 JSON 数据包 - """ - if not isinstance(page_ids, (tuple, list)): - page_ids = [page_ids] - data = { - 'page_ids': page_ids, - 'bind': int(bind), - 'append': int(append), - 'device_identifier': { - 'device_id': device_id, - 'uuid': uuid, - 'major': major, - 'minor': minor - } - } - return self._post( - 'shakearound/device/bindpage', - data=data - ) - - def get_shake_info(self, ticket): - """ - 获取摇周边的设备及用户信息 - 详情请参考 - http://mp.weixin.qq.com/wiki/3/34904a5db3d0ec7bb5306335b8da1faf.html - - :param ticket: 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟 - :return: 设备及用户信息 - """ - res = self._post( - 'shakearound/user/getshakeinfo', - data={ - 'ticket': ticket - }, - result_processor=lambda x: x['data'] - ) - return res - - def get_device_statistics(self, begin_date, end_date, device_id=None, - uuid=None, major=None, minor=None): - """ - 以设备为维度的数据统计接口 - http://mp.weixin.qq.com/wiki/0/8a24bcacad40fe7ee98d1573cb8a6764.html - - :param begin_date: 起始时间,最长时间跨度为30天 - :param end_date: 结束时间,最长时间跨度为30天 - :param device_id: 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 - :param uuid: UUID - :param major: major - :param minor: minor - """ - data = { - 'device_identifier': { - 'device_id': device_id, - 'uuid': uuid, - 'major': major, - 'minor': minor - }, - 'begin_date': self._to_timestamp(begin_date), - 'end_date': self._to_timestamp(end_date) - } - res = self._post( - 'shakearound/statistics/device', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def get_page_statistics(self, page_id, begin_date, end_date): - """ - 以页面为维度的数据统计接口 - 详情请参考 - http://mp.weixin.qq.com/wiki/0/8a24bcacad40fe7ee98d1573cb8a6764.html - - :param page_id: 页面 ID - :param begin_date: 起始时间,最长时间跨度为30天 - :param end_date: 结束时间,最长时间跨度为30天 - :return: 统计数据 - """ - res = self._post( - 'shakearound/statistics/page', - data={ - 'page_id': page_id, - 'begin_date': self._to_timestamp(begin_date), - 'end_date': self._to_timestamp(end_date), - }, - result_processor=lambda x: x['data'] - ) - return res - - def get_apply_status(self, apply_id): - """ - 查询设备ID申请审核状态 - 详情请参考 - http://mp.weixin.qq.com/wiki/15/b9e012f917e3484b7ed02771156411f3.html - - :param apply_id: 批次ID,申请设备ID时所返回的批次ID - :return: 批次状态信息 - """ - res = self._post( - 'shakearound/device/applystatus', - data={ - 'apply_id': apply_id, - }, - result_processor=lambda x: x['data'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/template.py b/sg_wechat_enterprise/we_api/client/api/template.py deleted file mode 100644 index 46daa41b..00000000 --- a/sg_wechat_enterprise/we_api/client/api/template.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatTemplate(BaseWeChatAPI): - - def set_industry(self, industry_id1, industry_id2): - """ - 设置所属行业 - 详情请参考 - http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html - - :param industry_id1: 公众号模板消息所属行业编号 - :param industry_id2: 公众号模板消息所属行业编号 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'template/api_set_industry', - data={ - 'industry_id1': industry_id1, - 'industry_id2': industry_id2 - } - ) - - def get(self, template_id_short): - """ - 获得模板ID - 详情请参考 - http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html - - :param template_id_short: 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 - :return: 模板 ID - """ - res = self._post( - 'template/api_add_template', - data={ - 'template_id_short': template_id_short - }, - result_processor=lambda x: x['template_id'] - ) - return res - - add = get diff --git a/sg_wechat_enterprise/we_api/client/api/user.py b/sg_wechat_enterprise/we_api/client/api/user.py deleted file mode 100644 index 320c905e..00000000 --- a/sg_wechat_enterprise/we_api/client/api/user.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import six - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatUser(BaseWeChatAPI): - - def get(self, user_id, lang='zh_CN'): - """ - 获取用户基本信息 - 详情请参考 - http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param lang: 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - user = client.user.get('openid') - - """ - assert lang in ('zh_CN', 'zh_TW', 'en'), 'lang can only be one of \ - zh_CN, zh_TW, en language codes' - return self._get( - 'user/info', - params={ - 'openid': user_id, - 'lang': lang - } - ) - - def get_followers(self, first_user_id=None): - """ - 获取关注者列表 - 详情请参考 - http://mp.weixin.qq.com/wiki/3/17e6919a39c1c53555185907acf70093.html - - :param first_user_id: 可选。第一个拉取的 OPENID,不填默认从头开始拉取 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - followers = client.user.get_followers() - - """ - params = {} - if first_user_id: - params['next_openid'] = first_user_id - return self._get( - 'user/get', - params=params - ) - - def update_remark(self, user_id, remark): - """ - 设置用户备注名 - 详情请参考 - http://mp.weixin.qq.com/wiki/10/bf8f4e3074e1cf91eb6518b6d08d223e.html - - :param user_id: 用户 ID 。 就是你收到的 `Message` 的 source - :param remark: 备注名 - :return: 返回的 JSON 数据包 - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - client.user.update_remark('openid', 'Remark') - - """ - return self._post( - 'user/info/updateremark', - data={ - 'openid': user_id, - 'remark': remark - } - ) - - def get_group_id(self, user_id): - """ - 获取用户所在分组 ID - - 详情请参考 - http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html - - :param user_id: 用户 ID - :return: 用户所在分组 ID - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - group_id = client.user.get_group_id('openid') - - """ - res = self._post( - 'groups/getid', - data={'openid': user_id}, - result_processor=lambda x: x['groupid'] - ) - return res - - def get_batch(self, user_list): - """ - 批量获取用户基本信息 - - 详情请参考 - http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html#.E6.89.B9.E9.87.8F.E8.8E.B7.E5.8F.96.E7.94.A8.E6.88.B7.E5.9F.BA.E6.9C.AC.E4.BF.A1.E6.81.AF - - :param user_id: user_list - :return: 用户信息的 list - - 使用示例:: - - from wechatpy import WeChatClient - - client = WeChatClient('appid', 'secret') - users = client.user.get_batch(['openid1', 'openid2']) - users = client.user.get_batch([ - {'openid': 'openid1', 'lang': 'zh-CN'}, - {'openid': 'openid2', 'lang': 'en'}, - ]) - - """ - if all((isinstance(x, six.string_types) for x in user_list)): - user_list = [{'openid': oid} for oid in user_list] - res = self._post( - 'user/info/batchget', - data={'user_list': user_list}, - result_processor=lambda x: x['user_info_list'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/api/wifi.py b/sg_wechat_enterprise/we_api/client/api/wifi.py deleted file mode 100644 index 1e8149e4..00000000 --- a/sg_wechat_enterprise/we_api/client/api/wifi.py +++ /dev/null @@ -1,195 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from datetime import datetime, date - -from optionaldict import optionaldict -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatWiFi(BaseWeChatAPI): - - API_BASE_URL = 'https://api.weixin.qq.com/bizwifi/' - - def list_shops(self, page_index=1, page_size=20): - """ - 获取门店列表 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/bcfb5d4578ea818b89913472cf2bbf8f.html - - :param page_index: 可选,分页下标,默认从1开始 - :param page_size: 可选,每页的个数,默认20个,最大20个 - :return: 返回的 JSON 数据包 - """ - res = self._post( - 'shop/list', - data={ - 'pageindex': page_index, - 'pagesize': page_size, - }, - result_processor=lambda x: x['data'] - ) - return res - - def get_shop(self, shop_id=0): - """ - 查询门店的WiFi信息 - http://mp.weixin.qq.com/wiki/15/bcfb5d4578ea818b89913472cf2bbf8f.html - - :param shop_id: 门店 ID - :return: 返回的 JSON 数据包 - """ - res = self._post( - 'shop/get', - data={ - 'shop_id': shop_id, - }, - result_processor=lambda x: x['data'] - ) - return res - - def add_device(self, shop_id, ssid, password, bssid): - """ - 添加设备 - - 详情请参考 - http://mp.weixin.qq.com/wiki/10/6232005bdc497f7cf8e19d4e843c70d2.html - - :param shop_id: 门店 ID - :param ssid: 无线网络设备的ssid。非认证公众号添加的ssid必需是“WX”开头(“WX”为大写字母), - 认证公众号和第三方平台无此限制;所有ssid均不能包含中文字符 - :param password: 无线网络设备的密码,大于8个字符,不能包含中文字符 - :param bssid: 无线网络设备无线mac地址,格式冒号分隔,字符长度17个,并且字母小写 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'device/add', - data={ - 'shop_id': shop_id, - 'ssid': ssid, - 'password': password, - 'bssid': bssid, - } - ) - - def list_devices(self, shop_id=None, page_index=1, page_size=20): - """ - 查询设备 - - 详情请参考 - http://mp.weixin.qq.com/wiki/10/6232005bdc497f7cf8e19d4e843c70d2.html - - :param shop_id: 可选,门店 ID - :param page_index: 可选,分页下标,默认从1开始 - :param page_size: 可选,每页的个数,默认20个,最大20个 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - shop_id=shop_id, - pageindex=page_index, - pagesize=page_size - ) - res = self._post( - 'device/list', - data=data, - result_processor=lambda x: x['data'] - ) - return res - - def delete_device(self, bssid): - """ - 删除设备 - - 详情请参考 - http://mp.weixin.qq.com/wiki/10/6232005bdc497f7cf8e19d4e843c70d2.html - - :param bssid: 无线网络设备无线mac地址,格式冒号分隔,字符长度17个,并且字母小写 - :return: 返回的 JSON 数据包 - """ - return self._post('device/delete', data={'bssid': bssid}) - - def get_qrcode_url(self, shop_id, img_id): - """ - 获取物料二维码图片网址 - - 详情请参考 - http://mp.weixin.qq.com/wiki/7/fcd0378ef00617fc276be2b3baa80973.html - - :param shop_id: 门店 ID - :param img_id: 物料样式编号:0-二维码,可用于自由设计宣传材料; - 1-桌贴(二维码),100mm×100mm(宽×高),可直接张贴 - :return: 二维码图片网址 - """ - res = self._post( - 'qrcode/get', - data={ - 'shop_id': shop_id, - 'img_id': img_id, - }, - result_processor=lambda x: x['data']['qrcode_url'] - ) - return res - - def set_homepage(self, shop_id, template_id, url=None): - """ - 设置商家主页 - - 详情请参考 - http://mp.weixin.qq.com/wiki/6/2732f3cf83947e0e4971aa8797ee9d6a.html - - :param shop_id: 门店 ID - :param template_id: 模板ID,0-默认模板,1-自定义url - :param url: 自定义链接,当template_id为1时必填 - :return: 返回的 JSON 数据包 - """ - data = { - 'shop_id': shop_id, - 'template_id': template_id, - } - if url: - data['struct'] = {'url': url} - return self._post('homepage/set', data=data) - - def get_homepage(self, shop_id): - """ - 查询商家主页 - - 详情请参考 - http://mp.weixin.qq.com/wiki/6/2732f3cf83947e0e4971aa8797ee9d6a.html - - :param shop_id: 门店 ID - :return: 返回的 JSON 数据包 - """ - res = self._post( - 'homepage/get', - data={'shop_id': shop_id}, - result_processor=lambda x: x['data'] - ) - return res - - def list_statistics(self, begin_date, end_date, shop_id=-1): - """ - Wi-Fi数据统计 - - 详情请参考 - http://mp.weixin.qq.com/wiki/8/dfa2b756b66fca5d9b1211bc18812698.html - - :param begin_date: 起始日期时间,最长时间跨度为30天 - :param end_date: 结束日期时间戳,最长时间跨度为30天 - :param shop_id: 可选,门店 ID,按门店ID搜索,-1为总统计 - :return: 返回的 JSON 数据包 - """ - if isinstance(begin_date, (datetime, date)): - begin_date = begin_date.strftime('%Y-%m-%d') - if isinstance(end_date, (datetime, date)): - end_date = end_date.strftime('%Y-%m-%d') - res = self._post( - 'statistics/list', - data={ - 'begin_date': begin_date, - 'end_date': end_date, - 'shop_id': shop_id - }, - result_processor=lambda x: x['data'] - ) - return res diff --git a/sg_wechat_enterprise/we_api/client/base.py b/sg_wechat_enterprise/we_api/client/base.py deleted file mode 100644 index 3a8edc49..00000000 --- a/sg_wechat_enterprise/we_api/client/base.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import sys -import time -import inspect -import logging - -import six -import requests -from wechatpy.utils import json, get_querystring -from wechatpy.session.memorystorage import MemoryStorage -from wechatpy.exceptions import WeChatClientException, APILimitedException -from wechatpy.client.api.base import BaseWeChatAPI - - -logger = logging.getLogger(__name__) - - -def _is_api_endpoint(obj): - return isinstance(obj, BaseWeChatAPI) - - -class BaseWeChatClient(object): - - API_BASE_URL = '' - - def __new__(cls, *args, **kwargs): - self = super(BaseWeChatClient, cls).__new__(cls) - if sys.version_info[:2] == (2, 6): - # Python 2.6 inspect.gemembers bug workaround - # http://bugs.python.org/issue1785 - for _class in cls.__mro__: - if issubclass(_class, BaseWeChatClient): - for name, api in _class.__dict__.items(): - if isinstance(api, BaseWeChatAPI): - api_cls = type(api) - api = api_cls(self) - setattr(self, name, api) - else: - api_endpoints = inspect.getmembers(self, _is_api_endpoint) - for name, api in api_endpoints: - api_cls = type(api) - api = api_cls(self) - setattr(self, name, api) - return self - - def __init__(self, appid, access_token=None, session=None, timeout=None, auto_retry=True): - self.appid = appid - self.expires_at = None - self.session = session or MemoryStorage() - self.timeout = timeout - self.auto_retry = auto_retry - - if isinstance(session, six.string_types): - from shove import Shove - from wechatpy.session.shovestorage import ShoveStorage - - querystring = get_querystring(session) - prefix = querystring.get('prefix', ['wechatpy'])[0] - - shove = Shove(session) - storage = ShoveStorage(shove, prefix) - self.session = storage - - if access_token: - self.session.set(self.access_token_key, access_token) - - @property - def access_token_key(self): - return '{0}_access_token'.format(self.appid) - - def _request(self, method, url_or_endpoint, **kwargs): - if not url_or_endpoint.startswith(('http://', 'https://')): - api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL) - url = '{base}{endpoint}'.format( - base=api_base_url, - endpoint=url_or_endpoint - ) - else: - url = url_or_endpoint - - # 群发消息上传视频接口地址 HTTPS 证书错误,暂时忽略证书验证 - if url.startswith('https://file.api.weixin.qq.com'): - kwargs['verify'] = False - - if 'params' not in kwargs: - kwargs['params'] = {} - if isinstance(kwargs['params'], dict) and \ - 'access_token' not in kwargs['params']: - kwargs['params']['access_token'] = self.access_token - if isinstance(kwargs.get('data', ''), dict): - body = json.dumps(kwargs['data'], ensure_ascii=False) - body = body.encode('utf-8') - kwargs['data'] = body - - kwargs['timeout'] = kwargs.get('timeout', self.timeout) - result_processor = kwargs.pop('result_processor', None) - res = requests.request( - method=method, - url=url, - **kwargs - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatClientException( - errcode=None, - errmsg=None, - client=self, - request=reqe.request, - response=reqe.response - ) - - return self._handle_result( - res, method, url, result_processor, **kwargs - ) - - def _decode_result(self, res): - try: - result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False) - except (TypeError, ValueError): - # Return origin response object if we can not decode it as JSON - logger.debug('Can not decode response as JSON', exc_info=True) - return res - return result - - def _handle_result(self, res, method=None, url=None, - result_processor=None, **kwargs): - if not isinstance(res, dict): - # Dirty hack around asyncio based AsyncWeChatClient - result = self._decode_result(res) - else: - result = res - - if not isinstance(result, dict): - return result - - if 'base_resp' in result: - # Different response in device APIs. Fuck tencent! - result = result['base_resp'] - if 'errcode' in result: - result['errcode'] = int(result['errcode']) - - if 'errcode' in result and result['errcode'] != 0: - errcode = result['errcode'] - errmsg = result.get('errmsg', errcode) - if errcode in (40001, 40014, 42001) and self.auto_retry: - logger.info('Access token expired, fetch a new one and retry request') - self.fetch_access_token() - access_token = self.session.get(self.access_token_key) - kwargs['params']['access_token'] = access_token - return self._request( - method=method, - url_or_endpoint=url, - result_processor=result_processor, - **kwargs - ) - elif errcode == 45009: - # api freq out of limit - raise APILimitedException( - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - else: - raise WeChatClientException( - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - - return result if not result_processor else result_processor(result) - - def get(self, url, **kwargs): - return self._request( - method='get', - url_or_endpoint=url, - **kwargs - ) - - _get = get - - def post(self, url, **kwargs): - return self._request( - method='post', - url_or_endpoint=url, - **kwargs - ) - - _post = post - - def _fetch_access_token(self, url, params): - """ The real fetch access token """ - logger.info('Fetching access token') - res = requests.get( - url=url, - params=params - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatClientException( - errcode=None, - errmsg=None, - client=self, - request=reqe.request, - response=reqe.response - ) - result = res.json() - if 'errcode' in result and result['errcode'] != 0: - raise WeChatClientException( - result['errcode'], - result['errmsg'], - client=self, - request=res.request, - response=res - ) - - expires_in = 7200 - if 'expires_in' in result: - expires_in = result['expires_in'] - self.session.set( - self.access_token_key, - result['access_token'], - expires_in - ) - self.expires_at = int(time.time()) + expires_in - return result - - def fetch_access_token(self): - raise NotImplementedError() - - @property - def access_token(self): - """ WeChat access token """ - access_token = self.session.get(self.access_token_key) - if access_token: - if not self.expires_at: - # user provided access_token, just return it - return access_token - - timestamp = time.time() - if self.expires_at - timestamp > 60: - return access_token - - self.fetch_access_token() - return self.session.get(self.access_token_key) diff --git a/sg_wechat_enterprise/we_api/component.py b/sg_wechat_enterprise/we_api/component.py deleted file mode 100644 index 79792961..00000000 --- a/sg_wechat_enterprise/we_api/component.py +++ /dev/null @@ -1,431 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.component - ~~~~~~~~~~~~~~~ - - This module provides client library for WeChat Open Platform - - :copyright: (c) 2015 by hunter007. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import time -import logging - -import six -import requests -import xmltodict - -from wechatpy.utils import to_text, to_binary, get_querystring, json -from wechatpy.fields import StringField, DateTimeField -from wechatpy.messages import MessageMetaClass -from wechatpy.session.memorystorage import MemoryStorage -from wechatpy.exceptions import WeChatClientException, APILimitedException -from wechatpy.crypto import WeChatCrypto -from wechatpy.client import WeChatComponentClient - - -logger = logging.getLogger(__name__) - - -class BaseComponentMessage(six.with_metaclass(MessageMetaClass)): - """Base class for all component messages and events""" - type = 'unknown' - appid = StringField('AppId') - create_time = DateTimeField('CreateTime') - - def __init__(self, message): - self._data = message - - def __repr__(self): - _repr = "{klass}({msg})".format( - klass=self.__class__.__name__, - msg=repr(self._data) - ) - if six.PY2: - return to_binary(_repr) - else: - return to_text(_repr) - - -class ComponentVerifyTicketMessage(BaseComponentMessage): - """ - component_verify_ticket协议 - """ - type = 'component_verify_ticket' - verify_ticket = StringField('ComponentVerifyTicket') - - -class ComponentUnauthorizedMessage(BaseComponentMessage): - """ - 取消授权通知 - """ - type = 'unauthorized' - authorizer_appid = StringField('AuthorizerAppid') - - -class BaseWeChatComponent(object): - - API_BASE_URL = 'https://api.weixin.qq.com/cgi-bin' - - def __init__(self, - component_appid, - component_appsecret, - component_token, - encoding_aes_key, - session=None): - """ - :param component_appid: 第三方平台appid - :param component_appsecret: 第三方平台appsecret - :param component_token: 公众号消息校验Token - :param encoding_aes_key: 公众号消息加解密Key - """ - self.component_appid = component_appid - self.component_appsecret = component_appsecret - self.expires_at = None - self.crypto = WeChatCrypto( - component_token, encoding_aes_key, component_appid) - self.session = session or MemoryStorage() - - if isinstance(session, six.string_types): - from shove import Shove - from wechatpy.session.shovestorage import ShoveStorage - - querystring = get_querystring(session) - prefix = querystring.get('prefix', ['wechatpy'])[0] - - shove = Shove(session) - storage = ShoveStorage(shove, prefix) - self.session = storage - - @property - def component_verify_ticket(self): - return self.session.get('component_verify_ticket') - - def _request(self, method, url_or_endpoint, **kwargs): - if not url_or_endpoint.startswith(('http://', 'https://')): - api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL) - url = '{base}{endpoint}'.format( - base=api_base_url, - endpoint=url_or_endpoint - ) - else: - url = url_or_endpoint - - if 'params' not in kwargs: - kwargs['params'] = {} - if isinstance(kwargs['params'], dict) and \ - 'component_access_token' not in kwargs['params']: - kwargs['params'][ - 'component_access_token'] = self.access_token - if isinstance(kwargs['data'], dict): - kwargs['data'] = json.dumps(kwargs['data']) - - res = requests.request( - method=method, - url=url, - **kwargs - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatClientException( - errcode=None, - errmsg=None, - client=self, - request=reqe.request, - response=reqe.response - ) - - return self._handle_result(res, method, url, **kwargs) - - def _handle_result(self, res, method=None, url=None, **kwargs): - result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False) - if 'errcode' in result: - result['errcode'] = int(result['errcode']) - - if 'errcode' in result and result['errcode'] != 0: - errcode = result['errcode'] - errmsg = result['errmsg'] - if errcode == 42001: - logger.info('Component access token expired, fetch a new one and retry request') - self.fetch_component_access_token() - kwargs['params']['component_access_token'] = self.session.get( - 'component_access_token' - ) - return self._request( - method=method, - url_or_endpoint=url, - **kwargs - ) - elif errcode == 45009: - # api freq out of limit - raise APILimitedException( - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - else: - raise WeChatClientException( - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - - return result - - def fetch_access_token(self): - """ - 获取 component_access_token - 详情请参考 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list\ - &t=resource/res_list&verify=1&id=open1419318587&token=&lang=zh_CN - - :return: 返回的 JSON 数据包 - """ - url = '{0}{1}'.format( - self.API_BASE_URL, - '/component/api_component_token' - ) - return self._fetch_access_token( - url=url, - data=json.dumps({ - 'component_appid': self.component_appid, - 'component_appsecret': self.component_appsecret, - 'component_verify_ticket': self.component_verify_ticket - }) - ) - - def _fetch_access_token(self, url, data): - """ The real fetch access token """ - logger.info('Fetching component access token') - res = requests.post( - url=url, - data=data - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatClientException( - errcode=None, - errmsg=None, - client=self, - request=reqe.request, - response=reqe.response - ) - result = res.json() - if 'errcode' in result and result['errcode'] != 0: - raise WeChatClientException( - result['errcode'], - result['errmsg'], - client=self, - request=res.request, - response=res - ) - - expires_in = 7200 - if 'expires_in' in result: - expires_in = result['expires_in'] - self.session.set( - 'component_access_token', - result['component_access_token'], - expires_in - ) - self.expires_at = int(time.time()) + expires_in - return result - - @property - def access_token(self): - """ WeChat component access token """ - access_token = self.session.get('component_access_token') - if access_token: - if not self.expires_at: - # user provided access_token, just return it - return access_token - - timestamp = time.time() - if self.expires_at - timestamp > 60: - return access_token - - self.fetch_access_token() - return self.session.get('component_access_token') - - def get(self, url, **kwargs): - return self._request( - method='get', - url_or_endpoint=url, - **kwargs - ) - - def post(self, url, **kwargs): - return self._request( - method='post', - url_or_endpoint=url, - **kwargs - ) - - -class WeChatComponent(BaseWeChatComponent): - - def create_preauthcode(self): - """ - 获取预授权码 - """ - return self.post( - '/component/api_create_preauthcode', - data={ - 'component_appid': self.component_appid - } - ) - - def query_auth(self, authorization_code): - """ - 使用授权码换取公众号的授权信息 - - :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明 - """ - return self.post( - '/component/api_query_auth', - data={ - 'component_appid': self.component_appid, - 'authorization_code': authorization_code - } - ) - - def refresh_authorizer_token( - self, authorizer_appid, authorizer_refresh_token): - """ - 获取(刷新)授权公众号的令牌 - - :params authorizer_appid: 授权方appid - :params authorizer_refresh_token: 授权方的刷新令牌 - """ - return self.post( - '/component/api_authorizer_token', - data={ - 'component_appid': self.component_appid, - 'authorizer_appid': authorizer_appid, - 'authorizer_refresh_token': authorizer_refresh_token - } - ) - - def get_authorizer_info(self, authorizer_appid): - """ - 获取授权方的账户信息 - - :params authorizer_appid: 授权方appid - """ - return self.post( - '/component/api_get_authorizer_info', - data={ - 'component_appid': self.component_appid, - 'authorizer_appid': authorizer_appid, - } - ) - - def get_authorizer_option(self, authorizer_appid, option_name): - """ - 获取授权方的选项设置信息 - - :params authorizer_appid: 授权公众号appid - :params option_name: 选项名称 - """ - return self.post( - '/component/api_get_authorizer_option', - data={ - 'component_appid': self.component_appid, - 'authorizer_appid': authorizer_appid, - 'option_name': option_name - } - ) - - def set_authorizer_option( - self, authorizer_appid, option_name, option_value): - """ - 设置授权方的选项信息 - - :params authorizer_appid: 授权公众号appid - :params option_name: 选项名称 - :params option_value: 设置的选项值 - """ - return self.post( - '/component/api_set_authorizer_option', - data={ - 'component_appid': self.component_appid, - 'authorizer_appid': authorizer_appid, - 'option_name': option_name, - 'option_value': option_value - } - ) - - def get_client_by_authorization_code(self, authorization_code): - """ - 通过授权码直接获取 Client 对象 - - :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明 - """ - result = self.query_auth(authorization_code) - access_token = result['authorization_info']['authorizer_access_token'] - refresh_token = result['authorization_info']['authorizer_refresh_token'] # NOQA - authorizer_appid = result['authorization_info']['authorizer_appid'] # noqa - return WeChatComponentClient( - authorizer_appid, self, access_token, refresh_token, - session=self.session - ) - - def get_client_by_appid(self, authorizer_appid): - """ - 通过 authorizer_appid 获取 Client 对象 - - :params authorizer_appid: 授权公众号appid - """ - access_token_key = '{0}_access_token'.format(authorizer_appid) - refresh_token_key = '{0}_refresh_token'.format(authorizer_appid) - access_token = self.session.get(access_token_key) - refresh_token = self.session.get(refresh_token_key) - - if not access_token: - ret = self.refresh_authorizer_token( - authorizer_appid, - refresh_token - ) - access_token = ret['authorizer_access_token'] - refresh_token = ret['authorizer_refresh_token'] - - return WeChatComponentClient( - authorizer_appid, - self, - access_token, - refresh_token, - session=self.session - ) - - def cache_component_verify_ticket(self, msg, signature, timestamp, nonce): - """ - 处理 wechat server 推送的 component_verify_ticket消息 - - :params msg: 加密内容 - :params signature: 消息签名 - :params timestamp: 时间戳 - :params nonce: 随机数 - """ - content = self.crypto.decrypt_message(msg, signature, timestamp, nonce) - message = xmltodict.parse(to_text(content))['xml'] - o = ComponentVerifyTicketMessage(message) - self.session.set(o.type, o.verify_ticket, 600) - - def get_unauthorized(self, msg, signature, timestamp, nonce): - """ - 处理取消授权通知 - - :params msg: 加密内容 - :params signature: 消息签名 - :params timestamp: 时间戳 - :params nonce: 随机数 - """ - content = self.crypto.decrypt_message(msg, signature, timestamp, nonce) - message = xmltodict.parse(to_text(content))['xml'] - return ComponentUnauthorizedMessage(message) diff --git a/sg_wechat_enterprise/we_api/crypto/__init__.py b/sg_wechat_enterprise/we_api/crypto/__init__.py deleted file mode 100644 index ecdeb77c..00000000 --- a/sg_wechat_enterprise/we_api/crypto/__init__.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.crypto - ~~~~~~~~~~~~~~~~ - - This module provides some crypto tools for WeChat and WeChat enterprise - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import time -import base64 - -from wechatpy.utils import to_text, to_binary, WeChatSigner -from wechatpy.exceptions import ( - InvalidAppIdException, - InvalidSignatureException -) -from wechatpy.crypto.base import BasePrpCrypto - - -def _get_signature(token, timestamp, nonce, encrypt): - signer = WeChatSigner() - signer.add_data(token, timestamp, nonce, encrypt) - return signer.signature - - -class PrpCrypto(BasePrpCrypto): - - def encrypt(self, text, app_id): - return self._encrypt(text, app_id) - - def decrypt(self, text, app_id): - return self._decrypt(text, app_id, InvalidAppIdException) - - -class BaseWeChatCrypto(object): - - def __init__(self, token, encoding_aes_key, _id): - encoding_aes_key = to_binary(encoding_aes_key + '=') - self.key = base64.b64decode(encoding_aes_key) - assert len(self.key) == 32 - self.token = token - self._id = _id - - def _check_signature(self, - signature, - timestamp, - nonce, - echo_str, - crypto_class=None): - _signature = _get_signature(self.token, timestamp, nonce, echo_str) - if _signature != signature: - raise InvalidSignatureException() - pc = crypto_class(self.key) - return pc.decrypt(echo_str, self._id) - - def _encrypt_message(self, - msg, - nonce, - timestamp=None, - crypto_class=None): - from wechatpy.replies import BaseReply - - xml = """ - - -{timestamp} - -""" - if isinstance(msg, BaseReply): - msg = msg.render() - timestamp = timestamp or to_binary(int(time.time())) - pc = crypto_class(self.key) - encrypt = to_text(pc.encrypt(msg, self._id)) - signature = _get_signature(self.token, timestamp, nonce, encrypt) - return to_text(xml.format( - encrypt=encrypt, - signature=signature, - timestamp=timestamp, - nonce=nonce - )) - - def _decrypt_message(self, - msg, - signature, - timestamp, - nonce, - crypto_class=None): - if not isinstance(msg, dict): - import xmltodict - - msg = xmltodict.parse(to_text(msg))['xml'] - - encrypt = msg['Encrypt'] - _signature = _get_signature(self.token, timestamp, nonce, encrypt) - if _signature != signature: - raise InvalidSignatureException() - pc = crypto_class(self.key) - return pc.decrypt(encrypt, self._id) - - -class WeChatCrypto(BaseWeChatCrypto): - - def __init__(self, token, encoding_aes_key, app_id): - super(WeChatCrypto, self).__init__(token, encoding_aes_key, app_id) - self.app_id = app_id - - def encrypt_message(self, msg, nonce, timestamp=None): - return self._encrypt_message(msg, nonce, timestamp, PrpCrypto) - - def decrypt_message(self, msg, signature, timestamp, nonce): - return self._decrypt_message( - msg, - signature, - timestamp, - nonce, - PrpCrypto - ) diff --git a/sg_wechat_enterprise/we_api/crypto/base.py b/sg_wechat_enterprise/we_api/crypto/base.py deleted file mode 100644 index 7f27fc06..00000000 --- a/sg_wechat_enterprise/we_api/crypto/base.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import struct -import socket -import base64 - -from wechatpy.utils import to_text, to_binary, random_string, byte2int -from wechatpy.crypto.pkcs7 import PKCS7Encoder -try: - from wechatpy.crypto.cryptography import WeChatCipher -except ImportError: - try: - from wechatpy.crypto.pycrypto import WeChatCipher - except ImportError: - raise Exception('You must install either cryptography or PyCrypto!') - - -class BasePrpCrypto(object): - - def __init__(self, key): - self.cipher = WeChatCipher(key) - - def get_random_string(self): - return random_string(16) - - def _encrypt(self, text, _id): - text = to_binary(text) - tmp_list = [] - tmp_list.append(to_binary(self.get_random_string())) - length = struct.pack(b'I', socket.htonl(len(text))) - tmp_list.append(length) - tmp_list.append(text) - tmp_list.append(to_binary(_id)) - - text = b''.join(tmp_list) - text = PKCS7Encoder.encode(text) - - ciphertext = to_binary(self.cipher.encrypt(text)) - return base64.b64encode(ciphertext) - - def _decrypt(self, text, _id, exception=None): - text = to_binary(text) - plain_text = self.cipher.decrypt(base64.b64decode(text)) - padding = byte2int(plain_text[-1]) - content = plain_text[16:-padding] - xml_length = socket.ntohl(struct.unpack(b'I', content[:4])[0]) - xml_content = to_text(content[4:xml_length + 4]) - from_id = to_text(content[xml_length + 4:]) - if from_id != _id: - exception = exception or Exception - raise exception() - return xml_content diff --git a/sg_wechat_enterprise/we_api/crypto/cryptography.py b/sg_wechat_enterprise/we_api/crypto/cryptography.py deleted file mode 100644 index d9f60a03..00000000 --- a/sg_wechat_enterprise/we_api/crypto/cryptography.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.backends import default_backend - - -class WeChatCipher(object): - - def __init__(self, key): - backend = default_backend() - self.cipher = Cipher( - algorithms.AES(key), - modes.CBC(key[:16]), - backend=backend - ) - - def encrypt(self, plaintext): - encryptor = self.cipher.encryptor() - return encryptor.update(plaintext) + encryptor.finalize() - - def decrypt(self, ciphertext): - decryptor = self.cipher.decryptor() - return decryptor.update(ciphertext) + decryptor.finalize() diff --git a/sg_wechat_enterprise/we_api/crypto/pkcs7.py b/sg_wechat_enterprise/we_api/crypto/pkcs7.py deleted file mode 100644 index 5278fdda..00000000 --- a/sg_wechat_enterprise/we_api/crypto/pkcs7.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.utils import to_binary, byte2int - - -class PKCS7Encoder(object): - block_size = 32 - - @classmethod - def encode(cls, text): - length = len(text) - padding_count = cls.block_size - length % cls.block_size - if padding_count == 0: - padding_count = cls.block_size - padding = to_binary(chr(padding_count)) - return text + padding * padding_count - - @classmethod - def decode(cls, decrypted): - padding = byte2int(decrypted[-1]) - if padding < 1 or padding > 32: - padding = 0 - return decrypted[:-padding] diff --git a/sg_wechat_enterprise/we_api/crypto/pycrypto.py b/sg_wechat_enterprise/we_api/crypto/pycrypto.py deleted file mode 100644 index 3a1e14a5..00000000 --- a/sg_wechat_enterprise/we_api/crypto/pycrypto.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from Crypto.Cipher import AES - - -class WeChatCipher(object): - - def __init__(self, key): - self.cipher = AES.new(key, AES.MODE_CBC, key[:16]) - - def encrypt(self, plaintext): - return self.cipher.encrypt(plaintext) - - def decrypt(self, ciphertext): - return self.cipher.decrypt(ciphertext) diff --git a/sg_wechat_enterprise/we_api/enterprise/__init__.py b/sg_wechat_enterprise/we_api/enterprise/__init__.py deleted file mode 100644 index d28f7a91..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.enterprise.parser import parse_message # NOQA -from wechatpy.enterprise.replies import create_reply # NOQA -from wechatpy.enterprise.crypto import WeChatCrypto # NOQA -from wechatpy.enterprise.client import WeChatClient # NOQA diff --git a/sg_wechat_enterprise/we_api/enterprise/client/__init__.py b/sg_wechat_enterprise/we_api/enterprise/client/__init__.py deleted file mode 100644 index 5198a9d3..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.client.base import BaseWeChatClient -from wechatpy.enterprise.client import api - - -class WeChatClient(BaseWeChatClient): - - API_BASE_URL = 'https://qyapi.weixin.qq.com/cgi-bin/' - - user = api.WeChatUser() - department = api.WeChatDepartment() - menu = api.WeChatMenu() - message = api.WeChatMessage() - tag = api.WeChatTag() - media = api.WeChatMedia() - misc = api.WeChatMisc() - agent = api.WeChatAgent() - batch = api.WeChatBatch() - jsapi = api.WeChatJSAPI() - material = api.WeChatMaterial() - oauth = api.WeChatOAuth() - shakearound = api.WeChatShakeAround() - service = api.WeChatService() - chat = api.WeChatChat() - - def __init__(self, corp_id, secret, access_token=None, - session=None, timeout=None, auto_retry=True): - super(WeChatClient, self).__init__( - corp_id, access_token, session, timeout, auto_retry - ) - self.corp_id = corp_id - self.secret = secret - - def fetch_access_token(self): - """ Fetch access token""" - return self._fetch_access_token( - url='https://qyapi.weixin.qq.com/cgi-bin/gettoken', - params={ - 'corpid': self.corp_id, - 'corpsecret': self.secret - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/__init__.py b/sg_wechat_enterprise/we_api/enterprise/client/api/__init__.py deleted file mode 100644 index 4c6232ec..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.enterprise.client.api.department import WeChatDepartment # NOQA -from wechatpy.enterprise.client.api.media import WeChatMedia # NOQA -from wechatpy.enterprise.client.api.message import WeChatMessage # NOQA -from wechatpy.enterprise.client.api.menu import WeChatMenu # NOQA -from wechatpy.enterprise.client.api.tag import WeChatTag # NOQA -from wechatpy.enterprise.client.api.user import WeChatUser # NOQA -from wechatpy.enterprise.client.api.misc import WeChatMisc # NOQA -from wechatpy.enterprise.client.api.agent import WeChatAgent # NOQA -from wechatpy.enterprise.client.api.batch import WeChatBatch # NOQA -from wechatpy.enterprise.client.api.jsapi import WeChatJSAPI # NOQA -from wechatpy.enterprise.client.api.material import WeChatMaterial # NOQA -from wechatpy.enterprise.client.api.oauth import WeChatOAuth # NOQA -from wechatpy.enterprise.client.api.shakearound import WeChatShakeAround # NOQA -from wechatpy.enterprise.client.api.service import WeChatService # NOQA -from wechatpy.enterprise.client.api.chat import WeChatChat # NOQA diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/agent.py b/sg_wechat_enterprise/we_api/enterprise/client/api/agent.py deleted file mode 100644 index e9d3b0bd..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/agent.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatAgent(BaseWeChatAPI): - - def get(self, agent_id): - """ - 获取企业号应用 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=获取企业号应用 - - :param agent_id: 授权方应用 id - :return: 返回的 JSON 数据包 - """ - return self._get( - 'agent/get', - params={ - 'agentid': agent_id - } - ) - - def set(self, - agent_id, - name=None, - description=None, - redirect_domain=None, - logo_media_id=None, - report_location_flag=0, - is_report_user=True, - is_report_enter=True): - """ - 设置企业号应用 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=设置企业号应用 - - :param agent_id: 企业应用的 id - :param name: 企业应用名称 - :param description: 企业应用详情 - :param redirect_domain: 企业应用可信域名 - :param logo_media_id: 企业应用头像的mediaid,通过多媒体接口上传图片获得mediaid - :param report_location_flag: 企业应用是否打开地理位置上报 0:不上报;1:进入会话上报;2:持续上报 - :param is_report_user: 是否接收用户变更通知 - :param is_report_enter: 是否上报用户进入应用事件 - :return: 返回的 JSON 数据包 - """ - agent_data = optionaldict() - agent_data['agentid'] = agent_id - agent_data['name'] = name - agent_data['description'] = description - agent_data['redirect_domain'] = redirect_domain - agent_data['logo_mediaid'] = logo_media_id - agent_data['report_location_flag'] = report_location_flag - agent_data['isreportuser'] = 1 if is_report_user else 0 - agent_data['isreportenter'] = 1 if is_report_enter else 0 - return self._post( - 'agent/set', - data=agent_data - ) - - def list(self): - """ - 获取应用概况列表 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=获取应用概况列表 - - :return: 应用概况列表 - """ - res = self._get('agent/list') - return res['agentlist'] diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/batch.py b/sg_wechat_enterprise/we_api/enterprise/client/api/batch.py deleted file mode 100644 index db775f5f..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/batch.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI -from wechatpy.utils import to_text - - -class WeChatBatch(BaseWeChatAPI): - - def invite_user(self, url, token, encoding_aes_key, user_ids=None, - party_ids=None, tag_ids=None, invite_tips=None): - """ - 邀请成员关注 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=异步任务接口 - - :param url: 企业应用接收企业号推送请求的访问协议和地址,支持http或https协议 - :param token: 用于生成签名 - :param encoding_aes_key: 用于消息体的加密,是AES密钥的Base64编码 - :param user_ids: 可选,成员ID列表,多个接收者用‘|’分隔,最多支持1000个。 - :param party_ids: 可选,部门ID列表,多个接收者用‘|’分隔,最多支持100个。 - :param tag_ids: 可选,标签ID列表,多个接收者用‘|’分隔。 - :param invite_tips: 可选,推送到微信上的提示语 - :return: 返回的 JSON 数据包 - """ - data = optionaldict() - data['callback'] = { - 'url': url, - 'token': token, - 'encodingaeskey': encoding_aes_key - } - if isinstance(user_ids, (tuple, list)): - user_ids = '|'.join(map(to_text, user_ids)) - if isinstance(party_ids, (tuple, list)): - party_ids = '|'.join(map(to_text, party_ids)) - if isinstance(tag_ids, (tuple, list)): - tag_ids = '|'.join(map(to_text, tag_ids)) - data['touser'] = user_ids - data['toparty'] = party_ids - data['totag'] = tag_ids - data['invite_tips'] = invite_tips - return self._post( - 'batch/inviteuser', - data=data - ) - - def sync_user(self, url, token, encoding_aes_key, media_id): - """ - 增量更新成员 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=异步任务接口 - - :param url: 企业应用接收企业号推送请求的访问协议和地址,支持http或https协议 - :param token: 用于生成签名 - :param encoding_aes_key: 用于消息体的加密,是AES密钥的Base64编码 - :param media_id: 上传的csv文件的media_id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'batch/syncuser', - data={ - 'media_id': media_id, - 'callback': { - 'url': url, - 'token': token, - 'encodingaeskey': encoding_aes_key - } - } - ) - - def replace_user(self, url, token, encoding_aes_key, media_id): - """ - 全量覆盖成员 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=异步任务接口 - - :param url: 企业应用接收企业号推送请求的访问协议和地址,支持http或https协议 - :param token: 用于生成签名 - :param encoding_aes_key: 用于消息体的加密,是AES密钥的Base64编码 - :param media_id: 上传的csv文件的media_id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'batch/replaceuser', - data={ - 'media_id': media_id, - 'callback': { - 'url': url, - 'token': token, - 'encodingaeskey': encoding_aes_key - } - } - ) - - def replace_party(self, url, token, encoding_aes_key, media_id): - """ - 全量覆盖部门 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=异步任务接口 - - :param url: 企业应用接收企业号推送请求的访问协议和地址,支持http或https协议 - :param token: 用于生成签名 - :param encoding_aes_key: 用于消息体的加密,是AES密钥的Base64编码 - :param media_id: 上传的csv文件的media_id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'batch/replaceparty', - data={ - 'media_id': media_id, - 'callback': { - 'url': url, - 'token': token, - 'encodingaeskey': encoding_aes_key - } - } - ) - - def get_result(self, job_id): - """ - 获取异步任务结果 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=异步任务接口 - - :param job_id: 异步任务id,最大长度为64字符 - :return: 返回的 JSON 数据包 - """ - return self._get( - 'batch/getresult', - params={ - 'jobid': job_id - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/chat.py b/sg_wechat_enterprise/we_api/enterprise/client/api/chat.py deleted file mode 100644 index 86c5eb18..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/chat.py +++ /dev/null @@ -1,273 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatChat(BaseWeChatAPI): - - def create(self, chat_id, name, owner, user_list): - """ - 创建会话 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param chat_id: 会话id。字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z, - 如果值内容为64bit无符号整型:要求值范围在[1, 2^63)之间, - [2^63, 2^64)为系统分配会话id区间 - :param name: 会话标题 - :param owner: 管理员userid,必须是该会话userlist的成员之一 - :param user_list: 会话成员列表,成员用userid来标识。会话成员必须在3人或以上,1000人以下 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'chat/create', - data={ - 'chatid': chat_id, - 'name': name, - 'owner': owner, - 'userlist': user_list, - } - ) - - def get(self, chat_id): - """ - 获取会话 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param chat_id: 会话 ID - :return: 会话信息 - """ - res = self._get('chat/get', params={'chatid': chat_id}) - return res['chat_info'] - - def update(self, chat_id, op_user, name=None, owner=None, - add_user_list=None, del_user_list=None): - """ - 修改会话 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param chat_id: 会话 ID - :param op_user: 操作人 userid - :param name: 会话标题 - :param owner: 管理员userid,必须是该会话userlist的成员之一 - :param add_user_list: 会话新增成员列表,成员用userid来标识 - :param del_user_list: 会话退出成员列表,成员用userid来标识 - :return: 返回的 JSON 数据包 - """ - data = optionaldict( - chatid=chat_id, - op_user=op_user, - name=name, - owner=owner, - add_user_list=add_user_list, - del_user_list=del_user_list, - ) - return self._post('chat/update', data=data) - - def quit(self, chat_id, op_user): - """ - 退出会话 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param chat_id: 会话 ID - :param op_user: 操作人 userid - :return: 返回的 JSON 数据包 - """ - return self._post( - 'chat/quit', - data={ - 'chatid': chat_id, - 'op_user': op_user, - } - ) - - def clear_notify(self, op_user, type, id): - """ - 清除会话未读状态 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param op_user: 会话所有者的userid - :param type: 会话类型:single|group,分别表示:单聊|群聊 - :param id: 会话值,为userid|chatid,分别表示:成员id|会话id - :return: 返回的 JSON 数据包 - """ - return self._post( - 'chat/clearnotify', - data={ - 'op_user': op_user, - 'chat': { - 'type': type, - 'id': id, - } - } - ) - - def set_mute(self, user_mute_list): - """ - 设置成员新消息免打扰 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param user_mute_list: 成员新消息免打扰参数,数组,最大支持10000个成员 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'chat/setmute', - data={'user_mute_list': user_mute_list} - ) - - def send_text(self, sender, receiver_type, receiver_id, content): - """ - 发送文本消息 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param sender: 发送人 - :param receiver_type: 接收人类型:single|group,分别表示:单聊|群聊 - :param receiver_id: 接收人的值,为userid|chatid,分别表示:成员id|会话id - :param content: 消息内容 - :return: 返回的 JSON 数据包 - """ - data = { - 'receiver': { - 'type': receiver_type, - 'id': receiver_id, - }, - 'sender': sender, - 'msgtype': 'text', - 'text': { - 'content': content, - } - } - return self._post('chat/send', data=data) - - def send_single_text(self, sender, receiver, content): - """ - 发送单聊文本消息 - - :param sender: 发送人 - :param receiver: 接收人成员 ID - :param content: 消息内容 - :return: 返回的 JSON 数据包 - """ - return self.send_text(sender, 'single', receiver, content) - - def send_group_text(self, sender, receiver, content): - """ - 发送群聊文本消息 - - :param sender: 发送人 - :param receiver: 会话 ID - :param content: 消息内容 - :return: 返回的 JSON 数据包 - """ - return self.send_text(sender, 'group', receiver, content) - - def send_image(self, sender, receiver_type, receiver_id, media_id): - """ - 发送图片消息 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param sender: 发送人 - :param receiver_type: 接收人类型:single|group,分别表示:单聊|群聊 - :param receiver_id: 接收人的值,为userid|chatid,分别表示:成员id|会话id - :param media_id: 图片媒体文件id,可以调用上传素材文件接口获取 - :return: 返回的 JSON 数据包 - """ - data = { - 'receiver': { - 'type': receiver_type, - 'id': receiver_id, - }, - 'sender': sender, - 'msgtype': 'image', - 'image': { - 'media_id': media_id, - } - } - return self._post('chat/send', data=data) - - def send_single_image(self, sender, receiver, media_id): - """ - 发送单聊图片消息 - - :param sender: 发送人 - :param receiver: 接收人成员 ID - :param media_id: 图片媒体文件id,可以调用上传素材文件接口获取 - :return: 返回的 JSON 数据包 - """ - return self.send_image(sender, 'single', receiver, media_id) - - def send_group_image(self, sender, receiver, media_id): - """ - 发送群聊图片消息 - - :param sender: 发送人 - :param receiver: 会话 ID - :param media_id: 图片媒体文件id,可以调用上传素材文件接口获取 - :return: 返回的 JSON 数据包 - """ - return self.send_image(sender, 'group', receiver, media_id) - - def send_file(self, sender, receiver_type, receiver_id, media_id): - """ - 发送文件消息 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=企业会话接口说明 - - :param sender: 发送人 - :param receiver_type: 接收人类型:single|group,分别表示:单聊|群聊 - :param receiver_id: 接收人的值,为userid|chatid,分别表示:成员id|会话id - :param media_id: 文件id,可以调用上传素材文件接口获取, 文件须大于4字节 - :return: 返回的 JSON 数据包 - """ - data = { - 'receiver': { - 'type': receiver_type, - 'id': receiver_id, - }, - 'sender': sender, - 'msgtype': 'file', - 'file': { - 'media_id': media_id, - } - } - return self._post('chat/send', data=data) - - def send_single_file(self, sender, receiver, media_id): - """ - 发送单聊文件消息 - - :param sender: 发送人 - :param receiver: 接收人成员 ID - :param media_id: 文件id,可以调用上传素材文件接口获取, 文件须大于4字节 - :return: 返回的 JSON 数据包 - """ - return self.send_file(sender, 'single', receiver, media_id) - - def send_group_file(self, sender, receiver, media_id): - """ - 发送群聊文件消息 - - :param sender: 发送人 - :param receiver: 会话 ID - :param media_id: 文件id,可以调用上传素材文件接口获取, 文件须大于4字节 - :return: 返回的 JSON 数据包 - """ - return self.send_file(sender, 'group', receiver, media_id) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/department.py b/sg_wechat_enterprise/we_api/enterprise/client/api/department.py deleted file mode 100644 index 096d6870..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/department.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatDepartment(BaseWeChatAPI): - - def create(self, name, parent_id=1, order=None, id=None): - """ - 创建部门 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理部门 - - :param name: 部门名称,长度限制为 1~64 个字符 - :param parent_id: 父亲部门 id ,根部门 id 为 1 - :return: 返回的 JSON 数据包 - """ - department_data = optionaldict() - department_data['name'] = name - department_data['parentid'] = parent_id - department_data['order'] = order - department_data['id'] = id - return self._post( - 'department/create', - data=dict(department_data) - ) - - def update(self, id, name=None, parent_id=None, order=None): - """ - 更新部门 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理部门 - - :param id: 部门 id - :param name: 部门名称 - :param parent_id: 父亲部门 id - :param order: 在父部门中的次序,从 1 开始,数字越大排序越靠后 - :return: 返回的 JSON 数据包 - """ - department_data = optionaldict() - department_data['id'] = id - department_data['name'] = name - department_data['parentid'] = parent_id - department_data['order'] = order - return self._post( - 'department/update', - data=dict(department_data) - ) - - def delete(self, id): - """ - 删除部门 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理部门 - - :param id: 部门 id - :return: 返回的 JSON 数据包 - """ - return self._get( - 'department/delete', - params={ - 'id': id - } - ) - - def get(self): - """ - 获取部门列表 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理部门 - - :return: 部门列表 - """ - res = self._get('department/list') - return res['department'] - - def get_users(self, id, status=0, fetch_child=0): - """ - 获取部门成员列表 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - - :param id: 部门 id - :param status: 0 获取全部员工,1 获取已关注成员列表, - 2 获取禁用成员列表,4 获取未关注成员列表。可叠加 - :param fetch_child: 1/0:是否递归获取子部门下面的成员 - :return: 部门成员列表 - """ - fetch_child = 1 if fetch_child else 0 - res = self._get( - 'user/simplelist', - params={ - 'department_id': id, - 'status': status, - 'fetch_child': fetch_child - } - ) - return res['userlist'] - - def get_users_list(self, id, status=0, fetch_child=0): - """ - 获取部门成员详情列表 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - - :param id: 部门 id - :param status: 0 获取全部员工,1 获取已关注成员列表, - 2 获取禁用成员列表,4 获取未关注成员列表。可叠加 - :param fetch_child: 1/0:是否递归获取子部门下面的成员 - :return: 部门成员列表 - """ - fetch_child = 1 if fetch_child else 0 - res = self._get( - 'user/list', - params={ - 'department_id': id, - 'status': status, - 'fetch_child': fetch_child - } - ) - return res['userlist'] \ No newline at end of file diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/jsapi.py b/sg_wechat_enterprise/we_api/enterprise/client/api/jsapi.py deleted file mode 100644 index b0661ea2..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/jsapi.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import time - -from wechatpy.client.api.base import BaseWeChatAPI -from wechatpy.utils import WeChatSigner - - -class WeChatJSAPI(BaseWeChatAPI): - - def get_ticket(self): - """ - 获取微信 JS-SDK ticket - - http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS%E6%8E%A5%E5%8F%A3 - - :return: 返回的 JSON 数据包 - """ - return self._get('get_jsapi_ticket') - - def get_jsapi_ticket(self): - """ - 获取微信 JS-SDK ticket - - 该方法会通过 session 对象自动缓存管理 ticket - - :return: ticket - """ - ticket = self.session.get('jsapi_ticket') - expires_at = self.session.get('jsapi_ticket_expires_at', 0) - if not ticket or expires_at < int(time.time()): - jsapi_ticket = self.get_ticket() - ticket = jsapi_ticket['ticket'] - expires_at = int(time.time()) + int(jsapi_ticket['expires_in']) - self.session.set('jsapi_ticket', ticket) - self.session.set('jsapi_ticket_expires_at', expires_at) - return ticket - - def get_jsapi_signature(self, noncestr, ticket, timestamp, url): - data = [ - 'noncestr={noncestr}'.format(noncestr=noncestr), - 'jsapi_ticket={ticket}'.format(ticket=ticket), - 'timestamp={timestamp}'.format(timestamp=timestamp), - 'url={url}'.format(url=url), - ] - signer = WeChatSigner(delimiter=b'&') - signer.add_data(*data) - return signer.signature diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/material.py b/sg_wechat_enterprise/we_api/enterprise/client/api/material.py deleted file mode 100644 index b5547904..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/material.py +++ /dev/null @@ -1,201 +0,0 @@ -# encoding: utf-8 -from __future__ import absolute_import, unicode_literals -import requests - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMaterial(BaseWeChatAPI): - - def add_articles(self, articles): - """ - 新增永久图文素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param articles: 图文素材数组 - :return: 返回的 JSON 数据包 - """ - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'title': article['title'], - 'content': article['content'], - 'author': article.get('author', ''), - 'content_source_url': article.get('content_source_url', ''), - 'digest': article.get('digest', ''), - 'show_cover_pic': article.get('show_cover_pic', '0') - }) - return self._post( - 'material/add_mpnews', - data={ - "mpnews": { - "articles": articles_data - } - } - ) - - def add(self, agent_id, media_type, media_file): - """ - 新增其它类型永久素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param agent_id: 企业应用的id - :param media_type: 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)普通文件(file) - :param media_file: 要上传的文件,一个 File-object - :return: 返回的 JSON 数据包 - """ - params = { - 'agentid': agent_id, - 'type': media_type, - } - return self._post( - url='material/add_material', - params=params, - files={ - 'media': media_file - } - ) - - def get_url(self, agent_id, media_id): - """ - 获取永久素材下载地址 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param agent_id: 企业应用的id - :param media_id: 媒体文件 ID - :return: 临时素材下载地址 - """ - parts = ( - 'https://qyapi.weixin.qq.com/cgi-bin/material/get', - '?access_token=', - self.access_token, - '&media_id=', - media_id, - '&agentid=', - agent_id, - ) - return ''.join(parts) - - def get(self, agent_id, media_id): - """ - 获取永久素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param agent_id: 企业应用的id - :param media_id: 媒体文件 ID - :return: requests 的 Response 实例 - """ - res = requests.get(self.get_url(agent_id, media_id)) - - return res - - def get_articles(self, agent_id, media_id): - """ - 获取永久素材:图文消息素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param agent_id: 企业应用的id - :param media_id: 媒体文件 ID - :return: 返回的 JSON 数据包 - """ - return self._get( - 'material/get', - params={ - 'agentid': agent_id, - 'media_id': media_id, - } - ) - - def delete(self, agent_id, media_id): - """ - 删除永久素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E5%88%A0%E9%99%A4%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90 - - :param agent_id: 企业应用的id - :param media_id: 媒体文件 ID - :return: 返回的 JSON 数据包 - """ - return self._get( - 'material/del', - params={ - 'agentid': agent_id, - 'media_id': media_id, - } - ) - - def update_articles(self, agent_id, media_id, articles): - """ - 修改永久图文素材 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BF%AE%E6%94%B9%E6%B0%B8%E4%B9%85%E5%9B%BE%E6%96%87%E7%B4%A0%E6%9D%90 - - :param media_id: 要修改的图文消息的 id - :param index: 要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为 0 - :param articles: 图文素材数组 - :return: 返回的 JSON 数据包 - """ - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'title': article['title'], - 'content': article['content'], - 'author': article.get('author', ''), - 'content_source_url': article.get('content_source_url', ''), - 'digest': article.get('digest', ''), - 'show_cover_pic': article.get('show_cover_pic', '0') - }) - return self._post( - 'material/update_news', - data={ - 'agentid': agent_id, - 'media_id': media_id, - 'articles': articles_data - } - ) - - def get_count(self, agent_id): - """ - 获取素材总数 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E7%B4%A0%E6%9D%90%E6%80%BB%E6%95%B0 - - :param agent_id: 企业应用的id - :return: 返回的 JSON 数据包 - """ - return self._get( - 'material/get_count', - params={ - 'agent_id': agent_id, - } - ) - - def batchget(self, agent_id, media_type, offset=0, count=20): - """ - 批量获取永久素材列表 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E7%B4%A0%E6%9D%90%E5%88%97%E8%A1%A8 - - :param agent_id: 企业应用的id - :param media_type: 媒体文件类型,分别有图文(mpnews)、图片(image)、 - 语音(voice)、视频(video)和文件(file) - :param offset: 从全部素材的该偏移位置开始返回,0 表示从第一个素材返回 - :param count: 返回素材的数量,取值在1到20之间 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'material/batchget', - data={ - 'agent_id': agent_id, - 'type': media_type, - 'offset': offset, - 'count': count - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/media.py b/sg_wechat_enterprise/we_api/enterprise/client/api/media.py deleted file mode 100644 index 0541ee54..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/media.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import requests - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMedia(BaseWeChatAPI): - - def upload(self, media_type, media_file): - """ - 上传临时素材文件 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E4%B8%B4%E6%97%B6%E7%B4%A0%E6%9D%90%E6%96%87%E4%BB%B6 - - :param media_type: 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和普通文件(file) - :param media_file: 要上传的文件,一个 File-object - :return: 返回的 JSON 数据包 - """ - return self._post( - 'media/upload', - params={ - 'type': media_type - }, - files={ - 'media': media_file - } - ) - - def upload_image(self, media_file): - """ - 上传卡券logo - :param media_file: 要上传的文件 - :return: - """ - return self._post( - 'media/uploadimg', - params={ - 'type': 'card_logo' - }, - files={ - 'media': media_file - } - ) - - def download(self, media_id): - """ - 获取临时素材文件 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E4%B8%B4%E6%97%B6%E7%B4%A0%E6%9D%90%E6%96%87%E4%BB%B6 - - :param media_id: 媒体文件 ID - :return: requests 的 Response 实例 - """ - return requests.get(self.get_url(media_id)) - - def get_url(self, media_id): - """ - 获取临时素材下载地址 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E4%B8%B4%E6%97%B6%E7%B4%A0%E6%9D%90%E6%96%87%E4%BB%B6 - - :param media_id: 媒体文件 ID - :return: 临时素材下载地址 - """ - parts = ( - 'https://qyapi.weixin.qq.com/cgi-bin/media/get', - '?access_token=', - self.access_token, - '&media_id=', - media_id - ) - return ''.join(parts) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/menu.py b/sg_wechat_enterprise/we_api/enterprise/client/api/menu.py deleted file mode 100644 index 2b41f882..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/menu.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI -from wechatpy.exceptions import WeChatClientException - - -class WeChatMenu(BaseWeChatAPI): - - def create(self, agent_id, menu_data): - return self._post( - 'menu/create', - params={ - 'agentid': agent_id - }, - data=menu_data - ) - - def get(self, agent_id): - try: - return self._get( - 'menu/get', - params={ - 'agentid': agent_id - } - ) - except WeChatClientException as e: - if e.errcode == 46003: - # menu not exist - return None - else: - raise e - - def delete(self, agent_id): - return self._get( - 'menu/delete', - params={ - 'agentid': agent_id - } - ) - - def update(self, agent_id, menu_data): - self.delete(agent_id) - return self.create(agent_id, menu_data) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/message.py b/sg_wechat_enterprise/we_api/enterprise/client/api/message.py deleted file mode 100644 index b6a93e28..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/message.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMessage(BaseWeChatAPI): - - def _send_message(self, agent_id, user_ids, party_ids='', - tag_ids='', msg=None): - msg = msg or {} - if isinstance(user_ids, (tuple, list)): - user_ids = '|'.join(user_ids) - if isinstance(party_ids, (tuple, list)): - party_ids = '|'.join(party_ids) - if isinstance(tag_ids, (tuple, list)): - tag_ids = '|'.join(tag_ids) - - data = { - 'touser': user_ids, - 'toparty': party_ids, - 'totag': tag_ids, - 'agentid': agent_id - } - data.update(msg) - return self._post( - 'message/send', - data=data - ) - - def send_text(self, agent_id, user_ids, content, - party_ids='', tag_ids='', safe=0): - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'text', - 'text': {'content': content}, - 'safe': safe - } - ) - - def send_image(self, agent_id, user_ids, media_id, - party_ids='', tag_ids='', safe=0): - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'image', - 'image': { - 'media_id': media_id - }, - 'safe': safe - } - ) - - def send_voice(self, agent_id, user_ids, media_id, - party_ids='', tag_ids='', safe=0): - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'voice', - 'voice': { - 'media_id': media_id - }, - 'safe': safe - } - ) - - def send_video(self, agent_id, user_ids, media_id, title=None, - description=None, party_ids='', tag_ids='', safe=0): - video_data = optionaldict() - video_data['media_id'] = media_id - video_data['title'] = title - video_data['description'] = description - - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'video', - 'video': dict(video_data), - 'safe': safe - } - ) - - def send_file(self, agent_id, user_ids, media_id, - party_ids='', tag_ids='', safe=0): - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'file', - 'file': { - 'media_id': media_id - }, - 'safe': safe - } - ) - - def send_articles(self, agent_id, user_ids, articles, - party_ids='', tag_ids=''): - articles_data = [] - for article in articles: - articles_data.append({ - 'title': article['title'], - 'description': article['description'], - 'url': article['url'], - 'picurl': article['image'] - }) - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'news', - 'news': { - 'articles': articles_data - } - } - ) - - def send_mp_articles(self, agent_id, user_ids, articles, - party_ids='', tag_ids='', safe=0): - articles_data = [] - for article in articles: - articles_data.append({ - 'thumb_media_id': article['thumb_media_id'], - 'author': article['author'], - 'title': article['title'], - 'content': article['content'], - 'content_source_url': article['content_source_url'], - 'digest': article['digest'], - 'show_cover_pic': article['show_cover_pic'] - }) - return self._send_message( - agent_id, - user_ids, - party_ids, - tag_ids, - msg={ - 'msgtype': 'mpnews', - 'mpnews': { - 'articles': articles_data - }, - 'safe': safe - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/misc.py b/sg_wechat_enterprise/we_api/enterprise/client/api/misc.py deleted file mode 100644 index bec63870..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/misc.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatMisc(BaseWeChatAPI): - - def get_wechat_ips(self): - """ - 获取微信服务器 IP 列表 - - :return: IP 地址列表 - """ - res = self._get('getcallbackip') - return res['ip_list'] diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/oauth.py b/sg_wechat_enterprise/we_api/enterprise/client/api/oauth.py deleted file mode 100644 index cf9e40d7..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/oauth.py +++ /dev/null @@ -1,51 +0,0 @@ -# encoding: utf-8 -from __future__ import absolute_import, unicode_literals -import six - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatOAuth(BaseWeChatAPI): - - OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize' - - def authorize_url(self, redirect_uri, state=None): - """ - 获取授权地址 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=OAuth%E9%AA%8C%E8%AF%81%E6%8E%A5%E5%8F%A3 - - :param redirect_url: 授权后重定向的回调链接地址 - :param state: 重定向后会带上 state 参数 - :return: 返回的 JSON 数据包 - """ - redirect_uri = six.moves.urllib.parse.quote(redirect_uri) - url_list = [ - self.OAUTH_BASE_URL, - '?appid=', - self._client.corp_id, - '&redirect_uri=', - redirect_uri, - '&response_type=code&scope=snsapi_base', - ] - if state: - url_list.extend(['&state=', state]) - url_list.append('#wechat_redirect') - return ''.join(url_list) - - def get_user_info(self, code): - """ - 根据 code 获取用户信息 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=OAuth%E9%AA%8C%E8%AF%81%E6%8E%A5%E5%8F%A3 - - :param code: 通过成员授权获取到的code - :return: 返回的 JSON 数据包 - """ - - return self._get( - 'user/getuserinfo', - params={ - 'code': code, - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/service.py b/sg_wechat_enterprise/we_api/enterprise/client/api/service.py deleted file mode 100644 index 07e2bcd8..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/service.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatService(BaseWeChatAPI): - - def get_provider_token(self, provider_secret): - """ - 获取应用提供商凭证 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=获取应用提供商凭证 - - :param provider_secret: 提供商的secret,在提供商管理页面可见 - :return: 返回的 JSON 数据包 - """ - return self._post( - 'service/get_provider_token', - data={ - 'corpid': self._client.corp_id, - 'provider_secret': provider_secret, - } - ) - - def get_login_info(self, provider_access_token, auth_code): - """ - 获取企业号管理员登录信息 - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=获取企业号管理员登录信息 - - :param provider_access_token: 服务提供商的 accesstoken - :param auth_code: OAuth 2.0 授权企业号管理员登录产生的 code - :return: 返回的 JSON 数据包 - """ - return self._post( - 'service/get_login_info', - params={ - 'provider_access_token': provider_access_token, - }, - data={ - 'auth_code': auth_code, - } - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/shakearound.py b/sg_wechat_enterprise/we_api/enterprise/client/api/shakearound.py deleted file mode 100644 index 27716498..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/shakearound.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -from __future__ import absolute_import, unicode_literals - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatShakeAround(BaseWeChatAPI): - - def get_shake_info(self, ticket): - """ - 获取摇周边的设备及用户信息 - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=获取设备及用户信息 - - :param ticket: 摇周边业务的ticket,可在摇到的 URL 中得到,ticket 生效时间为30分钟 - :return: 设备及用户信息 - """ - res = self._post( - 'shakearound/getshakeinfo', - data={ - 'ticket': ticket - } - ) - return res['data'] diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/tag.py b/sg_wechat_enterprise/we_api/enterprise/client/api/tag.py deleted file mode 100644 index c6be2733..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/tag.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatTag(BaseWeChatAPI): - - def create(self, name): - return self._post( - 'tag/create', - data={ - 'tagname': name - } - ) - - def update(self, tag_id, name): - return self._post( - 'tag/update', - data={ - 'tagid': tag_id, - 'tagname': name - } - ) - - def delete(self, tag_id): - return self._get( - 'tag/delete', - params={ - 'tagid': tag_id - } - ) - - def get_users(self, tag_id): - return self._get( - 'tag/get', - params={ - 'tagid': tag_id - } - ) - - def add_users(self, tag_id, user_ids): - return self._post( - 'tag/addtagusers', - data={ - 'tagid': tag_id, - 'userlist': user_ids - } - ) - - def delete_users(self, tag_id, user_ids): - return self._post( - 'tag/deltagusers', - data={ - 'tagid': tag_id, - 'userlist': user_ids - } - ) - - def list(self): - res = self._get('tag/list') - return res['taglist'] diff --git a/sg_wechat_enterprise/we_api/enterprise/client/api/user.py b/sg_wechat_enterprise/we_api/enterprise/client/api/user.py deleted file mode 100644 index 97b53b86..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/client/api/user.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from optionaldict import optionaldict - -from wechatpy.client.api.base import BaseWeChatAPI - - -class WeChatUser(BaseWeChatAPI): - - def create(self, user_id, name, department=None, position=None, - mobile=None, gender=0, tel=None, email=None, - weixin_id=None, extattr=None, avatar_mediaid=None, to_invite=True): - """ - 创建成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - user_data = optionaldict() - user_data['userid'] = user_id - user_data['name'] = name - user_data['gender'] = gender - user_data['department'] = department - user_data['position'] = position - user_data['mobile'] = mobile - user_data['tel'] = tel - user_data['email'] = email - user_data['weixinid'] = weixin_id - user_data['extattr'] = extattr - user_data['avatar_mediaid'] = avatar_mediaid - user_data['to_invite'] = to_invite - - return self._post( - 'user/create', - data=user_data - ) - - def update(self, user_id, name=None, department=None, position=None, - mobile=None, gender=None, tel=None, email=None, - weixin_id=None, enable=None, extattr=None, avatar_mediaid=None): - """ - 更新成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - user_data = optionaldict() - user_data['userid'] = user_id - user_data['name'] = name - user_data['gender'] = gender - user_data['department'] = department - user_data['position'] = position - user_data['mobile'] = mobile - user_data['tel'] = tel - user_data['email'] = email - user_data['weixinid'] = weixin_id - user_data['extattr'] = extattr - user_data['enable'] = enable - user_data['avatar_mediaid'] = avatar_mediaid - - return self._post( - 'user/update', - data=user_data - ) - - def delete(self, user_id): - """ - 删除成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - return self._get( - 'user/delete', - params={ - 'userid': user_id - } - ) - - def get(self, user_id): - """ - 获取成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - return self._get( - 'user/get', - params={ - 'userid': user_id - } - ) - - def verify(self, user_id): - return self._get( - 'user/authsucc', - params={ - 'userid': user_id - } - ) - - def get_info(self, agent_id, code): - return self._get( - 'user/getuserinfo', - params={ - 'agentid': agent_id, - 'code': code - } - ) - - def batch_delete(self, user_ids): - """ - 批量删除成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - return self._post( - 'user/batchdelete', - data={ - 'useridlist': user_ids - } - ) - - def list(self, department_id, fetch_child=False, status=0): - """ - 批量获取部门成员 - 详情请参考 http://qydev.weixin.qq.com/wiki/index.php?title=管理成员 - """ - res = self._get( - 'user/list', - params={ - 'department_id': department_id, - 'fetch_child': 1 if fetch_child else 0, - 'status': status - } - ) - return res['userlist'] - - def convert_to_openid(self, user_id, agent_id=None): - """ - user_id 转成 openid - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=Userid%E4%B8%8Eopenid%E4%BA%92%E6%8D%A2%E6%8E%A5%E5%8F%A3 - - :param user_id: 企业号内的成员 ID - :param agent_id: 可选,需要发送红包的应用ID,若只是使用微信支付和企业转账,则无需该参数 - :return: 返回的 JSON 数据包 - """ - data = optionaldict() - data['userid'] = user_id - data['agentid'] = agent_id - return self._post('user/convert_to_openid', data=data) - - def convert_to_user_id(self, openid): - """ - openid 转成 user_id - - 详情请参考 - http://qydev.weixin.qq.com/wiki/index.php?title=Userid%E4%B8%8Eopenid%E4%BA%92%E6%8D%A2%E6%8E%A5%E5%8F%A3 - - :param openid: 在使用微信支付、微信红包和企业转账之后,返回结果的openid - :return: 该 openid 在企业号中对应的成员 user_id - """ - res = self._post( - 'user/convert_to_userid', - data={'openid': openid} - ) - return res['userid'] diff --git a/sg_wechat_enterprise/we_api/enterprise/crypto.py b/sg_wechat_enterprise/we_api/enterprise/crypto.py deleted file mode 100644 index 7bd46f9c..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/crypto.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -from wechatpy.crypto import BasePrpCrypto, BaseWeChatCrypto -from wechatpy.enterprise.exceptions import InvalidCorpIdException - - -class PrpCrypto(BasePrpCrypto): - - def encrypt(self, text, corp_id): - return self._encrypt(text, corp_id) - - def decrypt(self, text, corp_id): - return self._decrypt(text, corp_id, InvalidCorpIdException) - - -class WeChatCrypto(BaseWeChatCrypto): - - def __init__(self, token, encoding_aes_key, corp_id): - super(WeChatCrypto, self).__init__(token, encoding_aes_key, corp_id) - self.corp_id = corp_id - - def check_signature(self, signature, timestamp, nonce, echo_str): - return self._check_signature( - signature, - timestamp, - nonce, - echo_str, - PrpCrypto - ) - - def encrypt_message(self, msg, nonce, timestamp=None): - return self._encrypt_message( - msg, - nonce, - timestamp, - PrpCrypto - ) - - def decrypt_message(self, msg, signature, timestamp, nonce): - return self._decrypt_message( - msg, - signature, - timestamp, - nonce, - PrpCrypto - ) diff --git a/sg_wechat_enterprise/we_api/enterprise/events.py b/sg_wechat_enterprise/we_api/enterprise/events.py deleted file mode 100644 index 6b3ef752..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/events.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.fields import IntegerField, BaseField -from wechatpy import events - - -EVENT_TYPES = {} - - -def register_event(event_type): - def register(cls): - EVENT_TYPES[event_type] = cls - return cls - return register - - -@register_event('subscribe') -class SubscribeEvent(events.SubscribeEvent): - """ - 成员关注事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('unsubscribe') -class UnsubscribeEvent(events.UnsubscribeEvent): - """ - 成员取消关注事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('click') -class ClickEvent(events.ClickEvent): - """ - 点击菜单拉取消息事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('xml') -class ViewEvent(events.ViewEvent): - """ - 点击菜单跳转链接事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('location') -class LocationEvent(events.LocationEvent): - """ - 上报地理位置事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('scancode_push') -class ScanCodePushEvent(events.ScanCodePushEvent): - """ - 扫码推事件的事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('scancode_waitmsg') -class ScanCodeWaitMsgEvent(events.ScanCodeWaitMsgEvent): - """ - 扫码推事件且弹出“消息接收中”提示框的事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('pic_sysphoto') -class PicSysPhotoEvent(events.PicSysPhotoEvent): - """ - 弹出系统拍照发图事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('pic_photo_or_album') -class PicPhotoOrAlbumEvent(events.PicPhotoOrAlbumEvent): - """ - 弹出拍照或相册发图事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('pic_weixin') -class PicWeChatEvent(events.PicWeChatEvent): - """ - 弹出微信相册发图器事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('location_select') -class LocationSelectEvent(events.LocationSelectEvent): - """ - 弹出地理位置选择器事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - - -@register_event('enter_agent') -class EnterAgentEvent(events.BaseEvent): - """ - 用户进入应用的事件推送 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - agent = IntegerField('AgentID', 0) - event = 'enter_agent' - - -@register_event('batch_job_result') -class BatchJobResultEvent(events.BaseEvent): - """ - 异步任务完成事件 - 详情请参阅 - http://qydev.weixin.qq.com/wiki/index.php?title=接受事件 - """ - event = 'batch_job_result' - batch_job = BaseField('BatchJob') - - @property - def job_id(self): - return self.batch_job['JobId'] - - @property - def job_type(self): - return self.batch_job['JobType'] - - @property - def err_code(self): - return self.batch_job['ErrCode'] - - @property - def err_msg(self): - return self.batch_job['ErrMsg'] diff --git a/sg_wechat_enterprise/we_api/enterprise/exceptions.py b/sg_wechat_enterprise/we_api/enterprise/exceptions.py deleted file mode 100644 index 80c136d8..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/exceptions.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.exceptions import WeChatException - - -class InvalidCorpIdException(WeChatException): - - def __init__(self, errcode=-40005, errmsg='Invalid corp_id'): - super(InvalidCorpIdException, self).__init__(errcode, errmsg) diff --git a/sg_wechat_enterprise/we_api/enterprise/messages.py b/sg_wechat_enterprise/we_api/enterprise/messages.py deleted file mode 100644 index 8ff46f89..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/messages.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from wechatpy.fields import IntegerField, StringField -from wechatpy import messages - - -MESSAGE_TYPES = {} - - -def register_message(msg_type): - def register(cls): - MESSAGE_TYPES[msg_type] = cls - return cls - return register - - -@register_message('text') -class TextMessage(messages.TextMessage): - agent = IntegerField('AgentID', 0) - - -@register_message('image') -class ImageMessage(messages.ImageMessage): - agent = IntegerField('AgentID', 0) - - -@register_message('voice') -class VoiceMessage(messages.VoiceMessage): - agent = IntegerField('AgentID', 0) - - -@register_message('shortvideo') -class ShortVideoMessage(messages.ShortVideoMessage): - agent = IntegerField('AgentID', 0) - - -@register_message('video') -class VideoMessage(messages.VideoMessage): - agent = IntegerField('AgentID', 0) - - -@register_message('location') -class LocationMessage(messages.LocationMessage): - agent = IntegerField('AgentID', 0) - -@register_message('link') -class LinkMessage(messages.LinkMessage): - agent = IntegerField('AgentID', 0) - pic_url = StringField('PicUrl') diff --git a/sg_wechat_enterprise/we_api/enterprise/parser.py b/sg_wechat_enterprise/we_api/enterprise/parser.py deleted file mode 100644 index a4b8b4e0..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/parser.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import xmltodict - -from wechatpy.messages import UnknownMessage -from wechatpy.utils import to_text -from wechatpy.enterprise.messages import MESSAGE_TYPES -from wechatpy.enterprise.events import EVENT_TYPES - - -def parse_message(xml): - if not xml: - return - message = xmltodict.parse(to_text(xml))['xml'] - message_type = message['MsgType'].lower() - if message_type == 'event': - event_type = message['Event'].lower() - message_class = EVENT_TYPES.get(event_type, UnknownMessage) - else: - message_class = MESSAGE_TYPES.get(message_type, UnknownMessage) - return message_class(message) diff --git a/sg_wechat_enterprise/we_api/enterprise/replies.py b/sg_wechat_enterprise/we_api/enterprise/replies.py deleted file mode 100644 index 0597f13a..00000000 --- a/sg_wechat_enterprise/we_api/enterprise/replies.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import six - -from wechatpy import replies -from wechatpy.fields import IntegerField - - -REPLY_TYPES = {} - - -def register_reply(reply_type): - def register(cls): - REPLY_TYPES[reply_type] = cls - return cls - return register - - -@register_reply('text') -class TextReply(replies.TextReply): - agent = IntegerField('AgentID', 0) - - -@register_reply('image') -class ImageReply(replies.ImageReply): - agent = IntegerField('AgentID', 0) - - -@register_reply('voice') -class VoiceReply(replies.VoiceReply): - agent = IntegerField('AgentID', 0) - - -@register_reply('video') -class VideoReply(replies.VideoReply): - agent = IntegerField('AgentID', 0) - - -@register_reply('news') -class ArticlesReply(replies.ArticlesReply): - agent = IntegerField('AgentID', 0) - - -def create_reply(reply, message=None, render=False): - r = None - if isinstance(reply, replies.BaseReply): - r = reply - if message: - r.source = message.target - r.target = message.source - r.agent = message.agent - elif isinstance(reply, six.string_types): - r = TextReply( - message=message, - content=reply - ) - elif isinstance(reply, (tuple, list)): - if len(reply) > 10: - raise AttributeError("Can't add more than 10 articles" - " in an ArticlesReply") - r = ArticlesReply( - message=message, - articles=reply - ) - if r and render: - return r.render() - return r diff --git a/sg_wechat_enterprise/we_api/events.py b/sg_wechat_enterprise/we_api/events.py deleted file mode 100644 index ac2d217f..00000000 --- a/sg_wechat_enterprise/we_api/events.py +++ /dev/null @@ -1,629 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.events - ~~~~~~~~~~~~~~~~ - - This module contains all the events WeChat callback uses. - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals - -from wechatpy.fields import ( - StringField, - FloatField, - IntegerField, - BaseField, - Base64DecodeField, - DateTimeField -) -from wechatpy.messages import BaseMessage - - -EVENT_TYPES = {} - - -def register_event(event_type): - """ - Register the event class so that they can be accessed from EVENT_TYPES - - :param event_type: Event type - """ - def register(cls): - EVENT_TYPES[event_type] = cls - return cls - return register - - -class BaseEvent(BaseMessage): - """Base class for all events""" - type = 'event' - event = '' - - -@register_event('subscribe') -class SubscribeEvent(BaseEvent): - """ - 用户关注事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'subscribe' - - -@register_event('unsubscribe') -class UnsubscribeEvent(BaseEvent): - """ - 用户取消关注事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'unsubscribe' - - -@register_event('subscribe_scan') -class SubscribeScanEvent(BaseEvent): - """ - 用户扫描二维码关注事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'subscribe_scan' - scene_id = StringField('EventKey') - ticket = StringField('Ticket') - - -@register_event('scan') -class ScanEvent(BaseEvent): - """ - 用户扫描二维码事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'scan' - scene_id = StringField('EventKey') - ticket = StringField('Ticket') - - -@register_event('location') -class LocationEvent(BaseEvent): - """ - 上报地理位置事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'location' - latitude = FloatField('Latitude', 0.0) - longitude = FloatField('Longitude', 0.0) - precision = FloatField('Precision', 0.0) - - -@register_event('click') -class ClickEvent(BaseEvent): - """ - 点击菜单拉取消息事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'click' - key = StringField('EventKey') - - -@register_event('xml') -class ViewEvent(BaseEvent): - """ - 点击菜单跳转链接事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html - """ - event = 'xml' - url = StringField('EventKey') - - -@register_event('masssendjobfinish') -class MassSendJobFinishEvent(BaseEvent): - """ - 群发消息任务完成事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html - """ - id = IntegerField('MsgID', 0) - event = 'masssendjobfinish' - status = StringField('Status') - total_count = IntegerField('TotalCount', 0) - filter_count = IntegerField('FilterCount', 0) - sent_count = IntegerField('SentCount', 0) - error_count = IntegerField('ErrorCount', 0) - - -@register_event('templatesendjobfinish') -class TemplateSendJobFinishEvent(BaseEvent): - """ - 模板消息任务完成事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html - """ - id = IntegerField('MsgID') - event = 'templatesendjobfinish' - status = StringField('Status') - - -class BaseScanCodeEvent(BaseEvent): - key = StringField('EventKey') - scan_code_info = BaseField('ScanCodeInfo', {}) - - @property - def scan_type(self): - return self.scan_code_info['ScanType'] - - @property - def scan_result(self): - return self.scan_code_info['ScanResult'] - - -@register_event('scancode_push') -class ScanCodePushEvent(BaseScanCodeEvent): - """ - 扫码推事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'scancode_push' - - -@register_event('scancode_waitmsg') -class ScanCodeWaitMsgEvent(BaseScanCodeEvent): - """ - 扫码推事件且弹出“消息接收中”提示框的事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'scancode_waitmsg' - - -class BasePictureEvent(BaseEvent): - key = StringField('EventKey') - pictures_info = BaseField('SendPicsInfo', {}) - - @property - def count(self): - return int(self.pictures_info['Count']) - - @property - def pictures(self): - if self.pictures_info['PicList']: - items = self.pictures_info['PicList']['item'] - if self.count > 1: - return items - return [items] - return [] - - -@register_event('pic_sysphoto') -class PicSysPhotoEvent(BasePictureEvent): - """ - 弹出系统拍照发图的事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'pic_sysphoto' - - -@register_event('pic_photo_or_album') -class PicPhotoOrAlbumEvent(BasePictureEvent): - """ - 弹出拍照或者相册发图的事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'pic_photo_or_album' - - -@register_event('pic_weixin') -class PicWeChatEvent(BasePictureEvent): - """ - 弹出微信相册发图器的事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'pic_weixin' - - -@register_event('location_select') -class LocationSelectEvent(BaseEvent): - """ - 弹出地理位置选择器的事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/981d772286d10d153a3dc4286c1ee5b5.html - """ - event = 'location_select' - key = StringField('EventKey') - location_info = BaseField('SendLocationInfo', {}) - - @property - def location_x(self): - return self.location_info['Location_X'] - - @property - def location_y(self): - return self.location_info['Location_Y'] - - @property - def location(self): - return self.location_x, self.location_y - - @property - def scale(self): - return self.location_info['Scale'] - - @property - def label(self): - return self.location_info['Label'] - - @property - def poiname(self): - return self.location_info['Poiname'] - - -@register_event('card_pass_check') -class CardPassCheckEvent(BaseEvent): - event = 'card_pass_check' - card_id = StringField('CardId') - - -@register_event('card_not_pass_check') -class CardNotPassCheckEvent(BaseEvent): - event = 'card_not_pass_check' - card_id = StringField('CardId') - - -@register_event('user_get_card') -class UserGetCardEvent(BaseEvent): - """ - 领取事件推送 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/16/28b34ee91675a04cb24853768debded4.html#.E9.A2.86.E5.8F.96.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81 - """ - event = 'user_get_card' - card_id = StringField('CardId') - is_given_by_friend = IntegerField('IsGiveByFriend') - friend = StringField('FriendUserName') - code = StringField('UserCardCode') - old_code = StringField('OldUserCardCode') - outer_id = StringField('OuterId') - - -@register_event('user_del_card') -class UserDeleteCardEvent(BaseEvent): - """ - 卡券删除事件推送 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/16/28b34ee91675a04cb24853768debded4.html#.E5.88.A0.E9.99.A4.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81 - """ - event = 'user_del_card' - card_id = StringField('CardId') - code = StringField('UserCardCode') - - -@register_event('user_consume_card') -class UserConsumeCardEvent(BaseEvent): - """ - 卡券核销事件推送 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/16/28b34ee91675a04cb24853768debded4.html#.E6.A0.B8.E9.94.80.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81 - """ - event = 'user_consume_card' - card_id = StringField('CardId') - code = StringField('UserCardCode') - consume_source = StringField('ConsumeSource') - location_id = StringField('LocationId') - staff = StringField('StaffOpenId') - - -@register_event('merchant_order') -class MerchantOrderEvent(BaseEvent): - event = 'merchant_order' - order_id = StringField('OrderId') - order_status = IntegerField('OrderStatus') - product_id = StringField('ProductId') - sku_info = StringField('SkuInfo') - - -@register_event('kf_create_session') -class KfCreateSessionEvent(BaseEvent): - event = 'kf_create_session' - account = StringField('KfAccount') - - -@register_event('kf_close_session') -class KfCloseSessionEvent(BaseEvent): - event = 'kf_close_session' - account = StringField('KfAccount') - - -@register_event('kf_switch_session') -class KfSwitchSessionEvent(BaseEvent): - event = 'kf_switch_session' - from_account = StringField('FromKfAccount') - to_account = StringField('ToKfAccount') - - -@register_event('device_text') -class DeviceTextEvent(BaseEvent): - event = 'device_text' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - session_id = StringField('SessionID') - content = Base64DecodeField('Content') - - -@register_event('device_bind') -class DeviceBindEvent(BaseEvent): - event = 'bind' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - session_id = StringField('SessionID') - content = Base64DecodeField('Content') - open_id = StringField('OpenID') - - -@register_event('device_unbind') -class DeviceUnbindEvent(BaseEvent): - event = 'unbind' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - session_id = StringField('SessionID') - content = Base64DecodeField('Content') - open_id = StringField('OpenID') - - -@register_event('device_subscribe_status') -class DeviceSubscribeStatusEvent(BaseEvent): - event = 'subscribe_status' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - open_id = StringField('OpenID') - op_type = IntegerField('OpType') - - -@register_event('device_unsubscribe_status') -class DeviceUnsubscribeStatusEvent(BaseEvent): - event = 'subscribe_status' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - open_id = StringField('OpenID') - op_type = IntegerField('OpType') - - -@register_event('shakearoundusershake') -class ShakearoundUserShakeEvent(BaseEvent): - event = 'shakearound_user_shake' - _chosen_beacon = BaseField('ChosenBeacon', {}) - _around_beacons = BaseField('AroundBeacons', {}) - - @property - def chosen_beacon(self): - beacon = self._chosen_beacon - if not beacon: - return {} - return { - 'uuid': beacon['Uuid'], - 'major': beacon['Major'], - 'minor': beacon['Minor'], - 'distance': float(beacon['Distance']), - } - - @property - def around_beacons(self): - beacons = self._around_beacons - if not beacons: - return [] - - ret = [] - for beacon in beacons['AroundBeacon']: - ret.append({ - 'uuid': beacon['Uuid'], - 'major': beacon['Major'], - 'minor': beacon['Minor'], - 'distance': float(beacon['Distance']), - }) - return ret - - -@register_event('poi_check_notify') -class PoiCheckNotifyEvent(BaseEvent): - event = 'poi_check_notify' - poi_id = StringField('PoiId') - uniq_id = StringField('UniqId') - result = StringField('Result') - message = StringField('Msg') - - -@register_event('wificonnected') -class WiFiConnectedEvent(BaseEvent): - event = 'wificconnected' - connect_time = IntegerField('ConnectTime') - expire_time = IntegerField('ExpireTime') - vendor_id = StringField('VendorId') - shop_id = StringField('PlaceId') - bssid = StringField('DeviceNo') - - -# ============================================================================ -# 微信认证事件推送 -# ============================================================================ -@register_event('qualification_verify_success') -class QualificationVerifySuccessEvent(BaseEvent): - """ - 资质认证成功事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'qualification_verify_success' - expired_time = DateTimeField('ExpiredTime') - - -@register_event('qualification_verify_fail') -class QualificationVerifyFailEvent(BaseEvent): - """ - 资质认证失败事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'qualification_verify_fail' - fail_time = DateTimeField('FailTime') - fail_reason = StringField('FailReason') - - -@register_event('naming_verify_success') -class NamingVerifySuccessEvent(BaseEvent): - """ - 名称认证成功事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'naming_verify_success' - expired_time = DateTimeField('ExpiredTime') - - -@register_event('naming_verify_fail') -class NamingVerifyFailEvent(BaseEvent): - """ - 名称认证失败事件 - - 客户端不打勾,但仍有接口权限。详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'naming_verify_fail' - fail_time = DateTimeField('FailTime') - fail_reason = StringField('FailReason') - - -@register_event('annual_renew') -class AnnualRenewEvent(BaseEvent): - """ - 年审通知事件 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'annual_renew' - expired_time = DateTimeField('ExpiredTime') - - -@register_event('verify_expired') -class VerifyExpiredEvent(BaseEvent): - """ - 认证过期失效通知 - - 详情请参阅 - http://mp.weixin.qq.com/wiki/1/7f81dec16b801b34629091094c099439.html - """ - event = 'verify_expired' - expired_time = DateTimeField('ExpiredTime') - - -@register_event('user_scan_product') -class UserScanProductEvent(BaseEvent): - """ - 打开商品主页事件 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/f4109a5e44b4bfbc7eb1337eb739f3e3.html - """ - event = 'user_scan_product' - standard = StringField('KeyStandard') - key = StringField('KeyStr') - country = StringField('Country') - province = StringField('Province') - city = StringField('City') - sex = IntegerField('Sex') - scene = IntegerField('Scene') - - -@register_event('user_scan_product_enter_session') -class UserScanProductEnterSessionEvent(BaseEvent): - """ - 进入公众号事件 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/f4109a5e44b4bfbc7eb1337eb739f3e3.html - """ - event = 'user_scan_product_enter_session' - standard = StringField('KeyStandard') - key = StringField('KeyStr') - - -@register_event('user_scan_product_async') -class UserScanProductAsyncEvent(BaseEvent): - """ - 地理位置信息异步推送事件 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/f4109a5e44b4bfbc7eb1337eb739f3e3.html - """ - event = 'user_scan_product_async' - standard = StringField('KeyStandard') - key = StringField('KeyStr') - region_code = StringField('RegionCode') - - -@register_event('user_scan_product_verify_action') -class UserScanProductVerifyActionEvent(BaseEvent): - """ - 商品审核结果事件 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/f4109a5e44b4bfbc7eb1337eb739f3e3.html - """ - event = 'user_scan_product_verify_action' - standard = StringField('KeyStandard') - key = StringField('KeyStr') - result = StringField('Result') - reason = StringField('ReasonMsg') - - -@register_event('subscribe_scan_product') -class SubscribeScanProductEvent(BaseEvent): - """ - 用户在商品主页中关注公众号事件 - - 详情请参考 - http://mp.weixin.qq.com/wiki/15/f4109a5e44b4bfbc7eb1337eb739f3e3.html - """ - event = 'subscribe_scan_product' - event_key = StringField('EventKey') - - @property - def scene(self): - return self.event_key.split('|', 1)[0] - - @property - def standard(self): - return self.event_key.split('|')[1] - - @property - def key(self): - return self.event_key.split('|')[2] diff --git a/sg_wechat_enterprise/we_api/exceptions.py b/sg_wechat_enterprise/we_api/exceptions.py deleted file mode 100644 index 891079c8..00000000 --- a/sg_wechat_enterprise/we_api/exceptions.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.exceptions - ~~~~~~~~~~~~~~~~~~~~ - - Basic exceptions definition. - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import six - -from wechatpy.utils import to_binary, to_text - - -class WeChatException(Exception): - """Base exception for wechatpy""" - - def __init__(self, errcode, errmsg): - """ - :param errcode: Error code - :param errmsg: Error message - """ - self.errcode = errcode - self.errmsg = errmsg - - def __str__(self): - if six.PY2: - return to_binary('Error code: {code}, message: {msg}'.format( - code=self.errcode, - msg=self.errmsg - )) - else: - return to_text('Error code: {code}, message: {msg}'.format( - code=self.errcode, - msg=self.errmsg - )) - - def __repr__(self): - _repr = '{klass}({code}, {msg}'.format( - klass=self.__class__.__name__, - code=self.errcode, - msg=self.errmsg - ) - if six.PY2: - return to_binary(_repr) - else: - return to_text(_repr) - - -class WeChatClientException(WeChatException): - """WeChat API client exception class""" - def __init__(self, errcode, errmsg, client=None, - request=None, response=None): - super(WeChatClientException, self).__init__(errcode, errmsg) - self.client = client - self.request = request - self.response = response - - -class InvalidSignatureException(WeChatException): - """Invalid signature exception class""" - - def __init__(self, errcode=-40001, errmsg='Invalid signature'): - super(InvalidSignatureException, self).__init__(errcode, errmsg) - - -class APILimitedException(WeChatClientException): - """WeChat API call limited exception class""" - pass - - -class InvalidAppIdException(WeChatException): - """Invalid app_id exception class""" - - def __init__(self, errcode=-40005, errmsg='Invalid AppId'): - super(InvalidAppIdException, self).__init__(errcode, errmsg) - - -class WeChatOAuthException(WeChatClientException): - """WeChat OAuth API exception class""" - pass - - -class WeChatPayException(WeChatClientException): - """WeChat Pay API exception class""" - def __init__(self, return_code, result_code=None, return_msg=None, - errcode=None, errmsg=None, client=None, - request=None, response=None): - """ - :param return_code: 返回状态码 - :param result_code: 业务结果 - :param return_msg: 返回信息 - :param errcode: 错误代码 - :param errmsg: 错误代码描述 - """ - super(WeChatPayException, self).__init__( - errcode, - errmsg, - client, - request, - response - ) - self.return_code = return_code - self.result_code = result_code - self.return_msg = return_msg - - def __str__(self): - if six.PY2: - return to_binary('Error code: {code}, message: {msg}'.format( - code=self.return_code, - msg=self.return_msg - )) - else: - return to_text('Error code: {code}, message: {msg}'.format( - code=self.return_code, - msg=self.return_msg - )) - - def __repr__(self): - _repr = '{klass}({code}, {msg})'.format( - klass=self.__class__.__name__, - code=self.return_code, - msg=self.return_msg - ) - if six.PY2: - return to_binary(_repr) - else: - return to_text(_repr) diff --git a/sg_wechat_enterprise/we_api/fields.py b/sg_wechat_enterprise/we_api/fields.py deleted file mode 100644 index 3797a6a3..00000000 --- a/sg_wechat_enterprise/we_api/fields.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.fields - ~~~~~~~~~~~~~~~~ - - This module defines some useful field types for parse WeChat messages - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import time -from datetime import datetime -import base64 -import copy - -import six - -from wechatpy.utils import to_text, to_binary, ObjectDict, timezone - -default_timezone = timezone('Asia/Shanghai') - - -class FieldDescriptor(object): - def __init__(self, field): - self.field = field - self.attr_name = field.name - - def __get__(self, instance, instance_type=None): - if instance is not None: - value = instance._data.get(self.attr_name) - if value is None: - value = copy.deepcopy(self.field.default) - instance._data[self.attr_name] = value - if isinstance(value, dict): - value = ObjectDict(value) - if value and not isinstance(value, (dict, list, tuple)) and \ - six.callable(self.field.converter): - value = self.field.converter(value) - return value - return self.field - - def __set__(self, instance, value): - instance._data[self.attr_name] = value - - -class BaseField(object): - converter = None - - def __init__(self, name, default=None): - self.name = name - self.default = default - - def to_xml(self, value): - raise NotImplementedError() - - def __repr__(self): - _repr = '{klass}({name})'.format( - klass=self.__class__.__name__, - name=repr(self.name) - ) - if six.PY2: - return to_binary(_repr) - else: - return to_text(_repr) - - def add_to_class(self, klass, name): - self.klass = klass - klass._fields[name] = self - setattr(klass, name, FieldDescriptor(self)) - - -class StringField(BaseField): - def __to_text(self, value): - return to_text(value) - - converter = __to_text - - def to_xml(self, value): - value = self.converter(value) - tpl = '<{name}>' - return tpl.format(name=self.name, value=value) - - -class IntegerField(BaseField): - converter = int - - def to_xml(self, value): - value = self.converter(value) if value is not None else self.default - tpl = '<{name}>{value}' - return tpl.format(name=self.name, value=value) - - -class DateTimeField(BaseField): - def __converter(self, value): - v = int(value) - return datetime.fromtimestamp(v, tz=default_timezone) - - converter = __converter - - def to_xml(self, value): - value = time.mktime(datetime.timetuple(value)) - value = int(value) - tpl = '<{name}>{value}' - return tpl.format(name=self.name, value=value) - - -class FloatField(BaseField): - converter = float - - def to_xml(self, value): - value = self.converter(value) if value is not None else self.default - tpl = '<{name}>{value}' - return tpl.format(name=self.name, value=value) - - -class ImageField(StringField): - def to_xml(self, value): - value = self.converter(value) - tpl = """ - - """ - return tpl.format(value=value) - - -class VoiceField(StringField): - def to_xml(self, value): - value = self.converter(value) - tpl = """ - - """ - return tpl.format(value=value) - - -class VideoField(StringField): - def to_xml(self, value): - media_id = self.converter(value['media_id']) - if 'title' in value: - title = self.converter(value['title']) - if 'description' in value: - description = self.converter(value['description']) - tpl = """""" - return tpl.format( - media_id=media_id, - title=title, - description=description - ) - - -class MusicField(StringField): - def to_xml(self, value): - thumb_media_id = self.converter(value['thumb_media_id']) - if 'title' in value: - title = self.converter(value['title']) - if 'description' in value: - description = self.converter(value['description']) - if 'music_url' in value: - music_url = self.converter(value['music_url']) - if 'hq_music_url' in value: - hq_music_url = self.converter(value['hq_music_url']) - tpl = """ - - <![CDATA[{title}]]> - - - - """ - return tpl.format( - thumb_media_id=thumb_media_id, - title=title, - description=description, - music_url=music_url, - hq_music_url=hq_music_url - ) - - -class ArticlesField(StringField): - def to_xml(self, articles): - article_count = len(articles) - items = [] - for article in articles: - title = self.converter(article.get('title', '')) - description = self.converter(article.get('description', '')) - image = self.converter(article.get('image', '')) - url = self.converter(article.get('url', '')) - item_tpl = """ - <![CDATA[{title}]]> - - - - """ - item = item_tpl.format( - title=title, - description=description, - image=image, - url=url - ) - items.append(item) - items_str = '\n'.join(items) - tpl = """{article_count} - {items}""" - return tpl.format( - article_count=article_count, - items=items_str - ) - - -class Base64EncodeField(StringField): - def __base64_encode(self, text): - return to_text(base64.b64encode(to_binary(text))) - - converter = __base64_encode - - -class Base64DecodeField(StringField): - def __base64_decode(self, text): - return to_text(base64.b64decode(to_binary(text))) - - converter = __base64_decode - - -class HardwareField(StringField): - def to_xml(self, value=None): - value = value or {'xml': 'myrank', 'action': 'ranklist'} - tpl = """<{name}> - - - """ - return tpl.format( - name=self.name, - view=value.get('xml'), - action=value.get('action') - ) diff --git a/sg_wechat_enterprise/we_api/messages.py b/sg_wechat_enterprise/we_api/messages.py deleted file mode 100644 index b53c6d1b..00000000 --- a/sg_wechat_enterprise/we_api/messages.py +++ /dev/null @@ -1,173 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.messages - ~~~~~~~~~~~~~~~~~~ - - This module defines all the messages you can get from WeChat server - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import copy -import six - -from wechatpy.fields import ( - BaseField, - StringField, - IntegerField, - DateTimeField, - FieldDescriptor -) -from wechatpy.utils import to_text, to_binary - - -MESSAGE_TYPES = {} - - -def register_message(msg_type): - def register(cls): - MESSAGE_TYPES[msg_type] = cls - return cls - return register - - -class MessageMetaClass(type): - """Metaclass for all messages""" - def __new__(cls, name, bases, attrs): - for b in bases: - if not hasattr(b, '_fields'): - continue - - for k, v in b.__dict__.items(): - if k in attrs: - continue - if isinstance(v, FieldDescriptor): - attrs[k] = copy.deepcopy(v.field) - - cls = super(MessageMetaClass, cls).__new__(cls, name, bases, attrs) - cls._fields = {} - - for name, field in cls.__dict__.items(): - if isinstance(field, BaseField): - field.add_to_class(cls, name) - return cls - - -class BaseMessage(six.with_metaclass(MessageMetaClass)): - """Base class for all messages and events""" - type = 'unknown' - id = IntegerField('MsgId', 0) - source = StringField('FromUserName') - target = StringField('ToUserName') - create_time = DateTimeField('CreateTime') - time = IntegerField('CreateTime') - - def __init__(self, message): - self._data = message - - def __repr__(self): - _repr = "{klass}({msg})".format( - klass=self.__class__.__name__, - msg=repr(self._data) - ) - if six.PY2: - return to_binary(_repr) - else: - return to_text(_repr) - - -@register_message('text') -class TextMessage(BaseMessage): - """ - 文本消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'text' - content = StringField('Content') - - -@register_message('image') -class ImageMessage(BaseMessage): - """ - 图片消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'image' - media_id = StringField('MediaId') - image = StringField('PicUrl') - - -@register_message('voice') -class VoiceMessage(BaseMessage): - """ - 语音消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'voice' - media_id = StringField('MediaId') - format = StringField('Format') - recognition = StringField('Recognition') - - -@register_message('shortvideo') -class ShortVideoMessage(BaseMessage): - """ - 短视频消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'shortvideo' - media_id = StringField('MediaId') - thumb_media_id = StringField('ThumbMediaId') - - -@register_message('video') -class VideoMessage(BaseMessage): - """ - 视频消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'video' - media_id = StringField('MediaId') - thumb_media_id = StringField('ThumbMediaId') - - -@register_message('location') -class LocationMessage(BaseMessage): - """ - 地理位置消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'location' - location_x = StringField('Location_X') - location_y = StringField('Location_Y') - scale = StringField('Scale') - label = StringField('Label') - - @property - def location(self): - return self.location_x, self.location_y - - -@register_message('link') -class LinkMessage(BaseMessage): - """ - 链接消息 - 详情请参阅 - http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html - """ - type = 'link' - title = StringField('Title') - description = StringField('Description') - url = StringField('Url') - - -class UnknownMessage(BaseMessage): - """未知消息类型""" - pass diff --git a/sg_wechat_enterprise/we_api/oauth/__init__.py b/sg_wechat_enterprise/we_api/oauth/__init__.py deleted file mode 100644 index e58aef38..00000000 --- a/sg_wechat_enterprise/we_api/oauth/__init__.py +++ /dev/null @@ -1,219 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.oauth - ~~~~~~~~~~~~~~~ - - This module provides OAuth2 library for WeChat - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -try: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) -except ImportError: - from pkg_resources import declare_namespace - declare_namespace(__name__) - -import requests -from six.moves.urllib.parse import quote -# from urllib import quote -from wechatpy.utils import json -from wechatpy.exceptions import WeChatOAuthException - - -class WeChatOAuth(object): - """微信公众平台 OAuth 网页授权 """ - - API_BASE_URL = 'https://api.weixin.qq.com/' - OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/' - - def __init__(self, app_id, secret, redirect_uri, - scope='snsapi_base', state=''): - """ - - :param app_id: 微信公众号 app_id - :param secret: 微信公众号 secret - :param redirect_uri: OAuth2 redirect URI - :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base`` - :param state: 可选,微信公众号 OAuth2 state - """ - self.app_id = app_id - self.secret = secret - self.redirect_uri = redirect_uri - self.scope = scope - self.state = state - - def _request(self, method, url_or_endpoint, **kwargs): - if not url_or_endpoint.startswith(('http://', 'https://')): - url = '{base}{endpoint}'.format( - base=self.API_BASE_URL, - endpoint=url_or_endpoint - ) - else: - url = url_or_endpoint - - if isinstance(kwargs.get('data', ''), dict): - body = json.dumps(kwargs['data'], ensure_ascii=False) - body = body.encode('utf-8') - kwargs['data'] = body - - res = requests.request( - method=method, - url=url, - **kwargs - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatOAuthException( - errcode=None, - errmsg=None, - client=self, - request=reqe.request, - response=reqe.response - ) - result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False) - - if 'errcode' in result and result['errcode'] != 0: - errcode = result['errcode'] - errmsg = result['errmsg'] - raise WeChatOAuthException( - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - - return result - - def _get(self, url, **kwargs): - return self._request( - method='get', - url_or_endpoint=url, - **kwargs - ) - - @property - def authorize_url(self): - """获取授权跳转地址 - - :return: URL 地址 - """ - redirect_uri = quote(self.redirect_uri) - url_list = [ - self.OAUTH_BASE_URL, - 'oauth2/authorize?appid=', - self.app_id, - '&redirect_uri=', - redirect_uri, - '&response_type=code&scope=', - self.scope - ] - if self.state: - url_list.extend(['&state=', self.state]) - url_list.append('#wechat_redirect') - return ''.join(url_list) - - @property - def qrconnect_url(self): - """生成扫码登录地址 - - :return: URL 地址 - """ - redirect_uri = quote(self.redirect_uri) - url_list = [ - self.OAUTH_BASE_URL, - 'qrconnect?appid=', - self.app_id, - '&redirect_uri=', - redirect_uri, - '&response_type=code&scope=', - 'snsapi_login' # scope - ] - if self.state: - url_list.extend(['&state=', self.state]) - url_list.append('#wechat_redirect') - return ''.join(url_list) - - def fetch_access_token(self, code): - """获取 access_token - - :param code: 授权完成跳转回来后 URL 中的 code 参数 - :return: JSON 数据包 1 - """ - res = self._get( - 'sns/oauth2/access_token', - params={ - 'appid': self.app_id, - 'secret': self.secret, - 'code': code, - 'grant_type': 'authorization_code' - } - ) - self.access_token = res['access_token'] - self.open_id = res['openid'] - self.refresh_token = res['refresh_token'] - self.expires_in = res['expires_in'] - return res - - def refresh_access_token(self, refresh_token): - """刷新 access token - - :param refresh_token: OAuth2 refresh token - :return: JSON 数据包 - """ - res = self._get( - 'sns/oauth2/refresh_token', - params={ - 'appid': self.app_id, - 'grant_type': 'refresh_token', - 'refresh_token': refresh_token - } - ) - self.access_token = res['access_token'] - self.open_id = res['openid'] - self.refresh_token = res['refresh_token'] - self.expires_in = res['expires_in'] - return res - - def get_user_info(self, openid=None, access_token=None, lang='zh_CN'): - """获取用户信息 - - :param openid: 可选,微信 openid,默认获取当前授权用户信息 - :param access_token: 可选,access_token,默认使用当前授权用户的 access_token - :param lang: 可选,语言偏好, 默认为 ``zh_CN`` - :return: JSON 数据包 - """ - openid = openid or self.open_id - access_token = access_token or self.access_token - return self._get( - 'sns/userinfo', - params={ - 'access_token': access_token, - 'openid': openid, - 'lang': lang - } - ) - - def check_access_token(self, openid=None, access_token=None): - """检查 access_token 有效性 - - :param openid: 可选,微信 openid,默认获取当前授权用户信息 - :param access_token: 可选,access_token,默认使用当前授权用户的 access_token - :return: 有效返回 True,否则 False - """ - openid = openid or self.open_id - access_token = access_token or self.access_token - res = self._get( - 'sns/auth', - params={ - 'access_token': access_token, - 'openid': openid - } - ) - if res['errcode'] == 0: - return True - return False diff --git a/sg_wechat_enterprise/we_api/parser.py b/sg_wechat_enterprise/we_api/parser.py deleted file mode 100644 index c9e4ab76..00000000 --- a/sg_wechat_enterprise/we_api/parser.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.parser - ~~~~~~~~~~~~~~~~ - This module provides functions for parsing WeChat messages - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import xmltodict - -from wechatpy.messages import MESSAGE_TYPES, UnknownMessage -from wechatpy.events import EVENT_TYPES -from wechatpy.utils import to_text - - -def parse_message(xml): - """ - 解析微信服务器推送的 XML 消息 - - :param xml: XML 消息 - :return: 解析成功返回对应的消息或事件,否则返回 ``UnknownMessage`` - """ - if not xml: - return - message = xmltodict.parse(to_text(xml))['xml'] - message_type = message['MsgType'].lower() - if message_type in ('event', 'device_event'): - event_type = message['Event'].lower() - # special event type for device_event - if message_type == 'device_event': - event_type = 'device_{event}'.format(event=event_type) - if event_type == 'subscribe' and message.get('EventKey'): - event_key = message['EventKey'] - if event_key.startswith(('scanbarcode|', 'scanimage|')): - event_type = 'subscribe_scan_product' - message['Event'] = event_type - else: - # Scan to subscribe with scene id event - event_type = 'subscribe_scan' - message['Event'] = event_type - message['EventKey'] = event_key.replace('qrscene_', '') - message_class = EVENT_TYPES.get(event_type, UnknownMessage) - else: - message_class = MESSAGE_TYPES.get(message_type, UnknownMessage) - return message_class(message) diff --git a/sg_wechat_enterprise/we_api/pay/__init__.py b/sg_wechat_enterprise/we_api/pay/__init__.py deleted file mode 100644 index c88a5fc3..00000000 --- a/sg_wechat_enterprise/we_api/pay/__init__.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import sys -import inspect -import logging - -import requests -import xmltodict -from xml.parsers.expat import ExpatError -from optionaldict import optionaldict - -from wechatpy.utils import random_string -from wechatpy.exceptions import WeChatPayException, InvalidSignatureException -from wechatpy.pay.utils import ( - calculate_signature, _check_signature, dict_to_xml -) -from wechatpy.pay.base import BaseWeChatPayAPI -from wechatpy.pay import api - - -logger = logging.getLogger(__name__) - - -def _is_api_endpoint(obj): - return isinstance(obj, BaseWeChatPayAPI) - - -class WeChatPay(object): - """ - 微信红包接口 - - :param appid: 微信公众号 appid - :param api_key: 商户 key - :param mch_id: 商户号 - :param sub_mch_id: 可选,子商户号,受理模式下必填 - :param mch_cert: 必填,商户证书路径 - :param mch_key: 必填,商户证书私钥路径 - """ - redpack = api.WeChatRedpack() - """红包接口""" - transfer = api.WeChatTransfer() - """企业付款接口""" - coupon = api.WeChatCoupon() - """代金券接口""" - order = api.WeChatOrder() - """订单接口""" - refund = api.WeChatRefund() - """退款接口""" - micropay = api.WeChatMicroPay() - """刷卡支付接口""" - tools = api.WeChatTools() - """工具类接口""" - jsapi = api.WeChatJSAPI() - - API_BASE_URL = 'https://api.mch.weixin.qq.com/' - - def __new__(cls, *args, **kwargs): - self = super(WeChatPay, cls).__new__(cls) - if sys.version_info[:2] == (2, 6): - import copy - # Python 2.6 inspect.gemembers bug workaround - # http://bugs.python.org/issue1785 - for name, _api in self.__class__.__dict__.items(): - if isinstance(_api, BaseWeChatPayAPI): - _api = copy.deepcopy(_api) - _api._client = self - setattr(self, name, _api) - else: - api_endpoints = inspect.getmembers(self, _is_api_endpoint) - for name, _api in api_endpoints: - api_cls = type(_api) - _api = api_cls(self) - setattr(self, name, _api) - return self - - def __init__(self, appid, api_key, mch_id, sub_mch_id=None, - mch_cert=None, mch_key=None): - """ - :param appid: 微信公众号 appid - :param api_key: 商户 key - :param mch_id: 商户号 - :param sub_mch_id: 可选,子商户号,受理模式下必填 - :param mch_cert: 商户证书路径 - :param mch_key: 商户证书私钥路径 - """ - self.appid = appid - self.api_key = api_key - self.mch_id = mch_id - self.sub_mch_id = sub_mch_id - self.mch_cert = mch_cert - self.mch_key = mch_key - - def _request(self, method, url_or_endpoint, **kwargs): - if not url_or_endpoint.startswith(('http://', 'https://')): - api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL) - url = '{base}{endpoint}'.format( - base=api_base_url, - endpoint=url_or_endpoint - ) - else: - url = url_or_endpoint - - if isinstance(kwargs.get('data', ''), dict): - data = optionaldict(kwargs['data']) - if 'mchid' not in data: - # Fuck Tencent - data.setdefault('mch_id', self.mch_id) - data.setdefault('sub_mch_id', self.sub_mch_id) - data.setdefault('nonce_str', random_string(32)) - sign = calculate_signature(data, self.api_key) - body = dict_to_xml(data, sign) - body = body.encode('utf-8') - kwargs['data'] = body - - # 商户证书 - if self.mch_cert and self.mch_key: - kwargs['cert'] = (self.mch_cert, self.mch_key) - - res = requests.request( - method=method, - url=url, - **kwargs - ) - try: - res.raise_for_status() - except requests.RequestException as reqe: - raise WeChatPayException( - return_code=None, - client=self, - request=reqe.request, - response=reqe.response - ) - - return self._handle_result(res) - - def _handle_result(self, res): - res.encoding = 'utf-8' - xml = res.text - try: - data = xmltodict.parse(xml)['xml'] - except (xmltodict.ParsingInterrupted, ExpatError): - # 解析 XML 失败 - logger.debug('WeChat payment result xml parsing error', exc_info=True) - return xml - - return_code = data['return_code'] - return_msg = data.get('return_msg') - result_code = data.get('result_code') - errcode = data.get('err_code') - errmsg = data.get('err_code_des') - if return_code != 'SUCCESS' or result_code != 'SUCCESS': - # 返回状态码不为成功 - raise WeChatPayException( - return_code, - result_code, - return_msg, - errcode, - errmsg, - client=self, - request=res.request, - response=res - ) - return data - - def get(self, url, **kwargs): - return self._request( - method='get', - url_or_endpoint=url, - **kwargs - ) - - def post(self, url, **kwargs): - return self._request( - method='post', - url_or_endpoint=url, - **kwargs - ) - - def check_signature(self, params): - return _check_signature(params, self.api_key) - - def parse_payment_result(self, xml): - """解析微信支付结果通知""" - try: - data = xmltodict.parse(xml) - except (xmltodict.ParsingInterrupted, ExpatError): - raise InvalidSignatureException() - - if not data or 'xml' not in data: - raise InvalidSignatureException() - - data = data['xml'] - sign = data.pop('sign', None) - real_sign = calculate_signature(data, self.api_key) - if sign != real_sign: - raise InvalidSignatureException() - - data['sign'] = sign - return data diff --git a/sg_wechat_enterprise/we_api/pay/api/__init__.py b/sg_wechat_enterprise/we_api/pay/api/__init__.py deleted file mode 100644 index 75e80fd8..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.pay.api.redpack import WeChatRedpack # NOQA -from wechatpy.pay.api.transfer import WeChatTransfer # NOQA -from wechatpy.pay.api.coupon import WeChatCoupon # NOQA -from wechatpy.pay.api.order import WeChatOrder # NOQA -from wechatpy.pay.api.refund import WeChatRefund # NOQA -from wechatpy.pay.api.tools import WeChatTools # NOQA -from wechatpy.pay.api.jsapi import WeChatJSAPI # NOQA -from wechatpy.pay.api.micropay import WeChatMicroPay # NOQA diff --git a/sg_wechat_enterprise/we_api/pay/api/coupon.py b/sg_wechat_enterprise/we_api/pay/api/coupon.py deleted file mode 100644 index 6be5d5d6..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/coupon.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import random -from datetime import datetime - -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatCoupon(BaseWeChatPayAPI): - - def send(self, user_id, stock_id, op_user_id=None, device_info=None, - out_trade_no=None): - """ - 发放代金券 - - :param user_id: 用户在公众号下的 openid - :param stock_id: 代金券批次 ID - :param op_user_id: 可选,操作员账号,默认为商户号 - :param device_info: 可选,微信支付分配的终端设备号 - :param out_trade_no: 可选,商户订单号,需保持唯一性,默认自动生成 - :return: 返回的结果信息 - """ - if not out_trade_no: - now = datetime.now() - out_trade_no = '{0}{1}{2}'.format( - self.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'appid': self.appid, - 'coupon_stock_id': stock_id, - 'openid': user_id, - 'openid_count': 1, - 'partner_trade_no': out_trade_no, - 'op_user_id': op_user_id, - 'device_info': device_info, - 'version': '1.0', - 'type': 'XML', - } - return self._post('mmpaymkttransfers/send_coupon', data=data) - - def query_stock(self, stock_id, op_user_id=None, device_info=None): - """ - 查询代金券批次 - - :param stock_id: 代金券批次 ID - :param op_user_id: 可选,操作员账号,默认为商户号 - :param device_info: 可选,微信支付分配的终端设备号 - :return: 返回的结果信息 - """ - data = { - 'appid': self.appid, - 'coupon_stock_id': stock_id, - 'op_user_id': op_user_id, - 'device_info': device_info, - 'version': '1.0', - 'type': 'XML', - } - return self._post('mmpaymkttransfers/query_coupon_stock', data=data) - - def query_coupon(self, coupon_id, user_id, - op_user_id=None, device_info=None): - """ - 查询代金券信息 - - :param coupon_id: 代金券 ID - :param user_id: 用户在公众号下的 openid - :param op_user_id: 可选,操作员账号,默认为商户号 - :param device_info: 可选,微信支付分配的终端设备号 - :return: 返回的结果信息 - """ - data = { - 'coupon_id': coupon_id, - 'openid': user_id, - 'appid': self.appid, - 'op_user_id': op_user_id, - 'device_info': device_info, - 'version': '1.0', - 'type': 'XML', - } - return self._post('promotion/query_coupon', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/jsapi.py b/sg_wechat_enterprise/we_api/pay/api/jsapi.py deleted file mode 100644 index 6f3196c4..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/jsapi.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import time - -from wechatpy.utils import random_string, to_text -from wechatpy.pay.base import BaseWeChatPayAPI -from wechatpy.pay.utils import calculate_signature - - -class WeChatJSAPI(BaseWeChatPayAPI): - - def get_jsapi_signature(self, prepay_id, timestamp=None, nonce_str=None): - """ - 获取 JSAPI 签名 - - :param prepay_id: 统一下单接口返回的 prepay_id 参数值 - :param timestamp: 可选,时间戳,默认为当前时间戳 - :param nonce_str: 可选,随机字符串,默认自动生成 - :return: 签名 - """ - data = { - 'appId': self.appid, - 'timeStamp': timestamp or to_text(int(time.time())), - 'nonceStr': nonce_str or random_string(32), - 'signType': 'MD5', - 'package': 'prepay_id={0}'.format(prepay_id), - } - return calculate_signature(data, self._client.api_key) - - def get_jsapi_params(self, prepay_id, timestamp=None, nonce_str=None): - """ - 获取 JSAPI 参数 - - :param prepay_id: 统一下单接口返回的 prepay_id 参数值 - :param timestamp: 可选,时间戳,默认为当前时间戳 - :param nonce_str: 可选,随机字符串,默认自动生成 - :return: 签名 - """ - data = { - 'appId': self.appid, - 'timeStamp': timestamp or to_text(int(time.time())), - 'nonceStr': nonce_str or random_string(32), - 'signType': 'MD5', - 'package': 'prepay_id={0}'.format(prepay_id), - } - sign = calculate_signature(data, self._client.api_key) - data['paySign'] = sign - return data diff --git a/sg_wechat_enterprise/we_api/pay/api/micropay.py b/sg_wechat_enterprise/we_api/pay/api/micropay.py deleted file mode 100644 index 5a77bf0d..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/micropay.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import random -from datetime import datetime - -from wechatpy.pay.utils import get_external_ip -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatMicroPay(BaseWeChatPayAPI): - def create(self, body, total_fee, auth_code, client_ip=None, out_trade_no=None, detail=None, attach=None, - fee_type='CNY', goods_tag=None, device_info=None, limit_pay=None): - """ - 刷卡支付接口 - :param device_info: 可选,终端设备号(商户自定义,如门店编号) - :param body: 商品描述 - :param detail: 可选,商品详情 - :param attach: 可选,附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 - :param client_ip: 可选,APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP - :param out_trade_no: 可选,商户订单号,默认自动生成 - :param total_fee: 总金额,单位分 - :param fee_type: 可选,符合ISO 4217标准的三位字母代码,默认人民币:CNY - :param goods_tag: 可选,商品标记,代金券或立减优惠功能的参数 - :param limit_pay: 可选,指定支付方式,no_credit--指定不能使用信用卡支付 - :param auth_code: 授权码,扫码支付授权码,设备读取用户微信中的条码或者二维码信息 - :return: 返回的结果数据 - """ - now = datetime.now() - if not out_trade_no: - out_trade_no = '{0}{1}{2}'.format( - self.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'appid': self.appid, - 'device_info': device_info, - 'body': body, - 'detail': detail, - 'attach': attach, - 'out_trade_no': out_trade_no, - 'total_fee': total_fee, - 'fee_type': fee_type, - 'spbill_create_ip': client_ip or get_external_ip(), - 'goods_tag': goods_tag, - 'limit_pay': limit_pay, - 'auth_code': auth_code, - } - return self._post('pay/micropay', data=data) - - def query(self, transaction_id=None, out_trade_no=None): - """ - 查询订单 - - :param transaction_id: 微信的订单号,优先使用 - :param out_trade_no: 商户系统内部的订单号,当没提供transaction_id时需要传这个。 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'transaction_id': transaction_id, - 'out_trade_no': out_trade_no, - } - return self._post('pay/orderquery', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/order.py b/sg_wechat_enterprise/we_api/pay/api/order.py deleted file mode 100644 index fb233845..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/order.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import time -import random -from datetime import datetime, timedelta - -from wechatpy.utils import timezone -from wechatpy.pay.utils import get_external_ip -from wechatpy.pay.base import BaseWeChatPayAPI -from wechatpy.utils import random_string, to_text -from wechatpy.pay.utils import calculate_signature - - -class WeChatOrder(BaseWeChatPayAPI): - - def create(self, trade_type, body, total_fee, notify_url, client_ip=None, - user_id=None, out_trade_no=None, detail=None, attach=None, - fee_type='CNY', time_start=None, time_expire=None, - goods_tag=None, product_id=None, device_info=None, limit_pay=None): - """ - 统一下单接口 - - :param trade_type: 交易类型,取值如下:JSAPI,NATIVE,APP,WAP - :param body: 商品描述 - :param total_fee: 总金额,单位分 - :param notify_url: 接收微信支付异步通知回调地址 - :param client_ip: 可选,APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP - :param user_id: 可选,用户在商户appid下的唯一标识。trade_type=JSAPI,此参数必传 - :param out_trade_no: 可选,商户订单号,默认自动生成 - :param detail: 可选,商品详情 - :param attach: 可选,附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 - :param fee_type: 可选,符合ISO 4217标准的三位字母代码,默认人民币:CNY - :param time_start: 可选,订单生成时间,默认为当前时间 - :param time_expire: 可选,订单失效时间,默认为订单生成时间后两小时 - :param goods_tag: 可选,商品标记,代金券或立减优惠功能的参数 - :param product_id: 可选,trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义 - :param device_info: 可选,终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" - :param limit_pay: 可选,指定支付方式,no_credit--指定不能使用信用卡支付 - :return: 返回的结果数据 - """ - now = datetime.fromtimestamp(time.time(), tz=timezone('Asia/Shanghai')) - hours_later = now + timedelta(hours=2) - if time_start is None: - time_start = now - if time_expire is None: - time_expire = hours_later - if not out_trade_no: - out_trade_no = '{0}{1}{2}'.format( - self.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'appid': self.appid, - 'device_info': device_info, - 'body': body, - 'detail': detail, - 'attach': attach, - 'out_trade_no': out_trade_no, - 'fee_type': fee_type, - 'total_fee': total_fee, - 'spbill_create_ip': client_ip or get_external_ip(), - 'time_start': time_start.strftime('%Y%m%d%H%M%S'), - 'time_expire': time_expire.strftime('%Y%m%d%H%M%S'), - 'goods_tag': goods_tag, - 'notify_url': notify_url, - 'trade_type': trade_type, - 'limit_pay': limit_pay, - 'product_id': product_id, - 'openid': user_id, - } - return self._post('pay/unifiedorder', data=data) - - def query(self, transaction_id=None, out_trade_no=None): - """ - 查询订单 - - :param transaction_id: 微信的订单号,优先使用 - :param out_trade_no: 商户系统内部的订单号,当没提供transaction_id时需要传这个。 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'transaction_id': transaction_id, - 'out_trade_no': out_trade_no, - } - return self._post('pay/orderquery', data=data) - - def close(self, out_trade_no): - """ - 关闭订单 - - :param out_trade_no: 商户系统内部的订单号 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'out_trade_no': out_trade_no, - } - return self._post('pay/closeorder', data=data) - - def get_appapi_params(self, prepay_id, timestamp=None, nonce_str=None): - """ - 获取 APP 支付参数 - - :param prepay_id: 统一下单接口返回的 prepay_id 参数值 - :param timestamp: 可选,时间戳,默认为当前时间戳 - :param nonce_str: 可选,随机字符串,默认自动生成 - :return: 签名 - """ - data = { - 'appid': self.appid, - 'partnerid': self.mch_id, - 'prepayid': prepay_id, - 'package': 'Sign=WXPay', - 'timestamp': timestamp or to_text(int(time.time())), - 'noncestr': nonce_str or random_string(32) - } - sign = calculate_signature(data, self._client.api_key) - data['sign'] = sign - return data - - def reverse(self, transaction_id=None, out_trade_no=None): - """ - 撤销订单 - - :param transaction_id: 可选,微信的订单号,优先使用 - :param out_trade_no: 可选,商户系统内部的订单号, - transaction_id、out_trade_no二选一, - 如果同时存在优先级:transaction_id> out_trade_no - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'transaction_id': transaction_id, - 'out_trade_no': out_trade_no, - } - return self._post('secapi/pay/reverse', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/redpack.py b/sg_wechat_enterprise/we_api/pay/api/redpack.py deleted file mode 100644 index 6c5a1b25..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/redpack.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import random -from datetime import datetime - -from wechatpy.pay.utils import get_external_ip -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatRedpack(BaseWeChatPayAPI): - - def send(self, user_id, total_amount, send_name, act_name, - wishing, remark, total_num=1, client_ip=None, - nick_name=None, min_value=None, - max_value=None, out_trade_no=None, logo_imgurl=None): - """ - 发送现金红包 - - :param user_id: 接收红包的用户在公众号下的 openid - :param total_amount: 红包金额,单位分 - :param send_name: 商户名称 - :param nick_name: 可选,提供方名称,默认和商户名称相同 - :param act_name: 活动名称 - :param wishing: 红包祝福语 - :param remark: 备注 - :param client_ip: 可选,调用接口的机器 IP 地址 - :param total_num: 可选,红包发放总人数,默认为 1 - :param min_value: 可选,最小红包金额,单位分 - :param max_value: 可选,最大红包金额,单位分 - :param out_trade_no: 可选,商户订单号,默认会自动生成 - :param logo_imgurl: 可选,商户 Logo 的 URL - :return: 返回的结果数据字典 - """ - if not out_trade_no: - now = datetime.now() - out_trade_no = '{0}{1}{2}'.format( - self.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'wxappid': self.appid, - 're_openid': user_id, - 'total_amount': total_amount, - 'nick_name': nick_name or send_name, - 'send_name': send_name, - 'act_name': act_name, - 'wishing': wishing, - 'remark': remark, - 'client_ip': client_ip or get_external_ip(), - 'total_num': total_num, - 'min_value': min_value or total_amount, - 'max_value': max_value or total_amount, - 'mch_billno': out_trade_no, - 'logo_imgurl': logo_imgurl, - } - return self._post('mmpaymkttransfers/sendredpack', data=data) - - def send_group(self, user_id, total_amount, send_name, act_name, wishing, - remark, total_num, client_ip=None, amt_type="ALL_RAND", - amt_list=None, out_trade_no=None, - logo_imgurl=None, watermark_imgurl=None, - banner_imgurl=None): - """ - 发送裂变红包 - - :param user_id: 接收红包的用户在公众号下的 openid - :param total_amount: 红包金额,单位分 - :param send_name: 商户名称 - :param act_name: 活动名称 - :param wishing: 红包祝福语 - :param remark: 备注 - :param total_num: 红包发放总人数 - :param client_ip: 可选,调用接口的机器 IP 地址 - :param amt_type: 可选,红包金额设置方式 - ALL_RAND—全部随机,商户指定总金额和红包发放总人数,由微信支付随机计算出各红包金额 - ALL_SPECIFIED—全部自定义 - SEED_SPECIFIED—种子红包自定义,其他随机 - :param amt_list: 可选,各红包具体金额,自定义金额时必须设置,单位分 - :param out_trade_no: 可选,商户订单号,默认会自动生成 - :param logo_imgurl: 可选,商户 Logo 的 URL - :param watermark_imgurl: 可选,背景水印图片 URL - :param banner_imgurl: 红包详情页面的 banner 图片 URL - :return: 返回的结果数据字典 - """ - if not out_trade_no: - now = datetime.now() - out_trade_no = '{0}{1}{2}'.format( - self._client.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'wxappid': self.appid, - 're_openid': user_id, - 'total_amount': total_amount, - 'send_name': send_name, - 'act_name': act_name, - 'wishing': wishing, - 'remark': remark, - 'total_num': total_num, - 'client_ip': client_ip or get_external_ip(), - 'amt_type': amt_type, - 'amt_list': amt_list, - 'mch_billno': out_trade_no, - 'logo_imgurl': logo_imgurl, - 'watermark_imgurl': watermark_imgurl, - 'banner_imgurl': banner_imgurl, - } - return self._post('mmpaymkttransfers/sendgroupredpack', data=data) - - def query(self, out_trade_no, bill_type='MCHT'): - """ - 查询红包发放记录 - - :param out_trade_no: 商户订单号 - :param bill_type: 可选,订单类型,目前固定为 MCHT - :return: 返回的红包发放记录信息 - """ - data = { - 'mch_billno': out_trade_no, - 'bill_type': bill_type, - 'appid': self.appid, - } - return self._post('mmpaymkttransfers/gethbinfo', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/refund.py b/sg_wechat_enterprise/we_api/pay/api/refund.py deleted file mode 100644 index 4e6bbc00..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/refund.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatRefund(BaseWeChatPayAPI): - - def apply(self, total_fee, refund_fee, out_refund_no, transaction_id=None, - out_trade_no=None, fee_type='CNY', op_user_id=None, - device_info=None,notify_url=None,refund_account='REFUND_SOURCE_UNSETTLED_FUNDS'): - """ - 申请退款 - - :param total_fee: 订单总金额,单位为分 - :param refund_fee: 退款总金额,单位为分 - :param out_refund_no: 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔 - :param transaction_id: 可选,微信订单号 - :param out_trade_no: 可选,商户系统内部的订单号,与 transaction_id 二选一 - :param fee_type: 可选,货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY - :param op_user_id: 可选,操作员帐号, 默认为商户号 - :param refund_account: 可选,退款资金来源,仅针对老资金流商户使用,默认使用未结算资金退款 - :param device_info: 可选,终端设备号 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'device_info': device_info, - 'transaction_id': transaction_id, - 'out_trade_no': out_trade_no, - 'out_refund_no': out_refund_no, - 'total_fee': total_fee, - 'refund_fee': refund_fee, - 'refund_fee_type': fee_type, - 'op_user_id': op_user_id if op_user_id else self.mch_id, - "notify_url":notify_url, - 'refund_account': refund_account, - - } - return self._post('secapi/pay/refund', data=data) - - def query(self, refund_id=None, out_refund_no=None, transaction_id=None, - out_trade_no=None, device_info=None): - """ - 查询退款 - - :param refund_id: 微信退款单号 - :param out_refund_no: 商户退款单号 - :param transaction_id: 微信订单号 - :param out_trade_no: 商户系统内部的订单号 - :param device_info: 可选,终端设备号 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'device_info': device_info, - 'transaction_id': transaction_id, - 'out_trade_no': out_trade_no, - 'out_refund_no': out_refund_no, - 'refund_id': refund_id, - } - return self._post('pay/refundquery', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/tools.py b/sg_wechat_enterprise/we_api/pay/api/tools.py deleted file mode 100644 index ab794081..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/tools.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from datetime import datetime, date - -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatTools(BaseWeChatPayAPI): - - def short_url(self, long_url): - """ - 长链接转短链接 - - :param long_url: 长链接 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'long_url': long_url, - } - return self._post('tools/shorturl', data=data) - - def download_bill(self, bill_date, bill_type='ALL', device_info=None): - """ - 下载对账单 - - :param bill_date: 下载对账单的日期 - :param bill_type: 账单类型,ALL,返回当日所有订单信息,默认值 - SUCCESS,返回当日成功支付的订单, - REFUND,返回当日退款订单, - REVOKED,已撤销的订单 - :param device_info: 微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单 - :return: 返回的结果数据 - """ - if isinstance(bill_date, (datetime, date)): - bill_date = bill_date.strftime('%Y%m%d') - - data = { - 'appid': self.appid, - 'bill_date': bill_date, - 'bill_type': bill_type, - 'device_info': device_info, - } - return self._post('pay/downloadbill', data=data) - - def auto_code_to_openid(self, auth_code): - """ - 授权码查询 openid 接口 - - :param auth_code: 扫码支付授权码,设备读取用户微信中的条码或者二维码信息 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'auth_code': auth_code, - } - return self._post('tools/authcodetoopenid', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/api/transfer.py b/sg_wechat_enterprise/we_api/pay/api/transfer.py deleted file mode 100644 index 8e207635..00000000 --- a/sg_wechat_enterprise/we_api/pay/api/transfer.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import random -from datetime import datetime - -from wechatpy.pay.utils import get_external_ip -from wechatpy.pay.base import BaseWeChatPayAPI - - -class WeChatTransfer(BaseWeChatPayAPI): - - def transfer(self, user_id, amount, desc, client_ip=None, - check_name='OPTION_CHECK', real_name=None, - out_trade_no=None, device_info=None): - """ - 企业付款接口 - - :param user_id: 接受收红包的用户在公众号下的 openid - :param amount: 付款金额,单位分 - :param desc: 付款说明 - :param client_ip: 可选,调用接口机器的 IP 地址 - :param check_name: 可选,校验用户姓名选项, - NO_CHECK:不校验真实姓名, - FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账), - OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功), - 默认为 OPTION_CHECK - :param real_name: 可选,收款用户真实姓名, - 如果check_name设置为FORCE_CHECK或OPTION_CHECK,则必填用户真实姓名 - :param out_trade_no: 可选,商户订单号,需保持唯一性,默认自动生成 - :param device_info: 可选,微信支付分配的终端设备号 - :return: 返回的结果信息 - """ - if not out_trade_no: - now = datetime.now() - out_trade_no = '{0}{1}{2}'.format( - self.mch_id, - now.strftime('%Y%m%d%H%M%S'), - random.randint(1000, 10000) - ) - data = { - 'mch_appid': self.appid, - 'mchid': self.mch_id, - 'device_info': device_info, - 'partner_trade_no': out_trade_no, - 'openid': user_id, - 'check_name': check_name, - 're_user_name': real_name, - 'amount': amount, - 'desc': desc, - 'spbill_create_ip': client_ip or get_external_ip(), - } - return self._post('mmpaymkttransfers/promotion/transfers', data=data) - - def query(self, out_trade_no): - """ - 企业付款查询接口 - - :param out_trade_no: 商户调用企业付款API时使用的商户订单号 - :return: 返回的结果数据 - """ - data = { - 'appid': self.appid, - 'partner_trade_no': out_trade_no, - } - return self._post('mmpaymkttransfers/gettransferinfo', data=data) diff --git a/sg_wechat_enterprise/we_api/pay/base.py b/sg_wechat_enterprise/we_api/pay/base.py deleted file mode 100644 index e1d17c28..00000000 --- a/sg_wechat_enterprise/we_api/pay/base.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - - -class BaseWeChatPayAPI(object): - """ WeChat Pay API base class """ - def __init__(self, client=None): - self._client = client - - def _get(self, url, **kwargs): - if getattr(self, 'API_BASE_URL', None): - kwargs['api_base_url'] = self.API_BASE_URL - return self._client.get(url, **kwargs) - - def _post(self, url, **kwargs): - if getattr(self, 'API_BASE_URL', None): - kwargs['api_base_url'] = self.API_BASE_URL - return self._client.post(url, **kwargs) - - @property - def appid(self): - return self._client.appid - - @property - def mch_id(self): - return self._client.mch_id - - @property - def sub_mch_id(self): - return self._client.sub_mch_id diff --git a/sg_wechat_enterprise/we_api/pay/utils.py b/sg_wechat_enterprise/we_api/pay/utils.py deleted file mode 100644 index 18691ca3..00000000 --- a/sg_wechat_enterprise/we_api/pay/utils.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -import copy -import hashlib -import socket - -import six - -from wechatpy.utils import to_binary, to_text - - -def format_url(params, api_key=None): - data = [to_binary('{0}={1}'.format(k, params[k])) for k in sorted(params) if params[k]] - if api_key: - data.append(to_binary('key={0}'.format(api_key))) - return b"&".join(data) - - -def calculate_signature(params, api_key): - url = format_url(params, api_key) - return to_text(hashlib.md5(url).hexdigest().upper()) - - -def _check_signature(params, api_key): - _params = copy.deepcopy(params) - sign = _params.pop('sign', '') - return sign == calculate_signature(_params, api_key) - - -def dict_to_xml(d, sign): - xml = ['\n'] - for k in sorted(d): - # use sorted to avoid test error on Py3k - v = d[k] - if isinstance(v, six.integer_types) or v.isdigit(): - xml.append('<{0}>{1}\n'.format(to_text(k), to_text(v))) - else: - xml.append( - '<{0}>\n'.format(to_text(k), to_text(v)) - ) - xml.append('\n'.format(to_text(sign))) - return ''.join(xml) - - -def get_external_ip(): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - wechat_ip = socket.gethostbyname('api.mch.weixin.qq.com') - sock.connect((wechat_ip, 80)) - addr, port = sock.getsockname() - sock.close() - return addr - except socket.error: - return '127.0.0.1' diff --git a/sg_wechat_enterprise/we_api/replies.py b/sg_wechat_enterprise/we_api/replies.py deleted file mode 100644 index cdcef25d..00000000 --- a/sg_wechat_enterprise/we_api/replies.py +++ /dev/null @@ -1,341 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.replies - ~~~~~~~~~~~~~~~~~~ - This module defines all kinds of replies you can send to WeChat - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import time -import six - -from wechatpy.fields import( - StringField, - IntegerField, - ImageField, - VoiceField, - VideoField, - MusicField, - ArticlesField, - Base64EncodeField, - HardwareField, -) -from wechatpy.messages import BaseMessage, MessageMetaClass -from wechatpy.utils import to_text, to_binary - - -REPLY_TYPES = {} - - -def register_reply(reply_type): - def register(cls): - REPLY_TYPES[reply_type] = cls - return cls - return register - - -class BaseReply(six.with_metaclass(MessageMetaClass)): - """Base class for all replies""" - source = StringField('FromUserName') - target = StringField('ToUserName') - time = IntegerField('CreateTime', time.time()) - type = 'unknown' - - def __init__(self, **kwargs): - self._data = {} - message = kwargs.pop('message', None) - if message and isinstance(message, BaseMessage): - if 'source' not in kwargs: - kwargs['source'] = message.target - if 'target' not in kwargs: - kwargs['target'] = message.source - if hasattr(message, 'agent') and 'agent' not in kwargs: - kwargs['agent'] = message.agent - if 'time' not in kwargs: - kwargs['time'] = time.time() - for name, value in kwargs.items(): - field = self._fields.get(name) - if field: - self._data[field.name] = value - else: - setattr(self, name, value) - - def render(self): - """Render reply from Python object to XML string""" - tpl = '\n{data}\n' - nodes = [] - msg_type = ''.format( - msg_type=self.type - ) - nodes.append(msg_type) - for name, field in self._fields.items(): - value = getattr(self, name, field.default) - node_xml = field.to_xml(value) - nodes.append(node_xml) - data = '\n'.join(nodes) - return tpl.format(data=data) - - def __str__(self): - if six.PY2: - return to_binary(self.render()) - else: - return to_text(self.render()) - - -@register_reply('empty') -class EmptyReply(BaseReply): - """ - 回复空串 - - 微信服务器不会对此作任何处理,并且不会发起重试 - """ - def __init__(self): - pass - - def render(self): - return '' - - -@register_reply('text') -class TextReply(BaseReply): - """ - 文本回复 - 详情请参阅 http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'text' - content = StringField('Content') - - -@register_reply('image') -class ImageReply(BaseReply): - """ - 图片回复 - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'image' - image = ImageField('Image') - - @property - def media_id(self): - return self.image - - @media_id.setter - def media_id(self, value): - self.image = value - - -@register_reply('voice') -class VoiceReply(BaseReply): - """ - 语音回复 - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'voice' - voice = VoiceField('Voice') - - @property - def media_id(self): - return self.voice - - @media_id.setter - def media_id(self, value): - self.voice = value - - -@register_reply('video') -class VideoReply(BaseReply): - """ - 视频回复 - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'video' - video = VideoField('Video', {}) - - @property - def media_id(self): - return self.video.get('media_id') - - @media_id.setter - def media_id(self, value): - video = self.video - video['media_id'] = value - self.video = video - - @property - def title(self): - return self.video.get('title') - - @title.setter - def title(self, value): - video = self.video - video['title'] = value - self.video = video - - @property - def description(self): - return self.video.get('description') - - @description.setter - def description(self, value): - video = self.video - video['description'] = value - self.video = video - - -@register_reply('music') -class MusicReply(BaseReply): - """ - 音乐回复 - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'music' - music = MusicField('Music', {}) - - @property - def thumb_media_id(self): - return self.music.get('thumb_media_id') - - @thumb_media_id.setter - def thumb_media_id(self, value): - music = self.music - music['thumb_media_id'] = value - self.music = music - - @property - def title(self): - return self.music.get('title') - - @title.setter - def title(self, value): - music = self.music - music['title'] = value - self.music = music - - @property - def description(self): - return self.music.get('description') - - @description.setter - def description(self, value): - music = self.music - music['description'] = value - self.music = music - - @property - def music_url(self): - return self.music.get('music_url') - - @music_url.setter - def music_url(self, value): - music = self.music - music['music_url'] = value - self.music = music - - @property - def hq_music_url(self): - return self.music.get('hq_music_url') - - @hq_music_url.setter - def hq_music_url(self, value): - music = self.music - music['hq_music_url'] = value - self.music = music - - -@register_reply('news') -class ArticlesReply(BaseReply): - """ - 图文回复 - 详情请参阅 - http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html - """ - type = 'news' - articles = ArticlesField('Articles', []) - - def add_article(self, article): - if len(self.articles) == 10: - raise AttributeError("Can't add more than 10 articles" - " in an ArticlesReply") - articles = self.articles - articles.append(article) - self.articles = articles - - -@register_reply('transfer_customer_service') -class TransferCustomerServiceReply(BaseReply): - """ - 将消息转发到多客服 - 详情请参阅 - http://mp.weixin.qq.com/wiki/5/ae230189c9bd07a6b221f48619aeef35.html - """ - type = 'transfer_customer_service' - - -@register_reply('device_text') -class DeviceTextReply(BaseReply): - type = 'device_text' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - session_id = StringField('SessionID') - content = Base64EncodeField('Content') - - -@register_reply('device_event') -class DeviceEventReply(BaseReply): - type = 'device_event' - event = StringField('Event') - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - session_id = StringField('SessionID') - content = Base64EncodeField('Content') - - -@register_reply('device_status') -class DeviceStatusReply(BaseReply): - type = 'device_status' - device_type = StringField('DeviceType') - device_id = StringField('DeviceID') - status = StringField('DeviceStatus') - - -@register_reply('hardware') -class HardwareReply(BaseReply): - type = 'hardware' - func_flag = IntegerField('FuncFlag', 0) - hardware = HardwareField('HardWare') - - -def create_reply(reply, message=None, render=False): - """ - Create a reply quickly - """ - r = None - if not reply: - r = EmptyReply() - elif isinstance(reply, BaseReply): - r = reply - if message: - r.source = message.target - r.target = message.source - elif isinstance(reply, six.string_types): - r = TextReply( - message=message, - content=reply - ) - elif isinstance(reply, (tuple, list)): - if len(reply) > 10: - raise AttributeError("Can't add more than 10 articles" - " in an ArticlesReply") - r = ArticlesReply( - message=message, - articles=reply - ) - if r and render: - return r.render() - return r diff --git a/sg_wechat_enterprise/we_api/session/__init__.py b/sg_wechat_enterprise/we_api/session/__init__.py deleted file mode 100644 index 197397bc..00000000 --- a/sg_wechat_enterprise/we_api/session/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - - -class SessionStorage(object): - - def get(self, key, default=None): - raise NotImplementedError() - - def set(self, key, value, ttl=None): - raise NotImplementedError() - - def delete(self, key): - raise NotImplementedError() - - def __getitem__(self, key): - self.get(key) - - def __setitem__(self, key, value): - self.set(key, value) - - def __delitem__(self, key): - self.delete(key) diff --git a/sg_wechat_enterprise/we_api/session/memcachedstorage.py b/sg_wechat_enterprise/we_api/session/memcachedstorage.py deleted file mode 100644 index dda13bc8..00000000 --- a/sg_wechat_enterprise/we_api/session/memcachedstorage.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.session import SessionStorage -from wechatpy.utils import to_text -from wechatpy.utils import json - - -class MemcachedStorage(SessionStorage): - - def __init__(self, mc, prefix='wechatpy'): - for method_name in ('get', 'set', 'delete'): - assert hasattr(mc, method_name) - self.mc = mc - self.prefix = prefix - - def key_name(self, key): - return '{0}:{1}'.format(self.prefix, key) - - def get(self, key, default=None): - key = self.key_name(key) - value = self.mc.get(key) - if value is None: - return default - return json.loads(to_text(value)) - - def set(self, key, value, ttl=None): - if value is None: - return - key = self.key_name(key) - value = json.dumps(value) - self.mc.set(key, value) - - def delete(self, key): - key = self.key_name(key) - self.mc.delete(key) diff --git a/sg_wechat_enterprise/we_api/session/memorystorage.py b/sg_wechat_enterprise/we_api/session/memorystorage.py deleted file mode 100644 index 7eb2e4e6..00000000 --- a/sg_wechat_enterprise/we_api/session/memorystorage.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.session import SessionStorage - - -class MemoryStorage(SessionStorage): - - def __init__(self): - self._data = {} - - def get(self, key, default=None): - return self._data.get(key, default) - - def set(self, key, value, ttl=None): - if value is None: - return - self._data[key] = value - - def delete(self, key): - self._data.pop(key, None) diff --git a/sg_wechat_enterprise/we_api/session/redisstorage.py b/sg_wechat_enterprise/we_api/session/redisstorage.py deleted file mode 100644 index 42171e6f..00000000 --- a/sg_wechat_enterprise/we_api/session/redisstorage.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.session import SessionStorage -from wechatpy.utils import to_text -from wechatpy.utils import json - - -class RedisStorage(SessionStorage): - - def __init__(self, redis, prefix='wechatpy'): - for method_name in ('get', 'set', 'delete'): - assert hasattr(redis, method_name) - self.redis = redis - self.prefix = prefix - - def key_name(self, key): - return '{0}:{1}'.format(self.prefix, key) - - def get(self, key, default=None): - key = self.key_name(key) - value = self.redis.get(key) - if value is None: - return default - return json.loads(to_text(value)) - - def set(self, key, value, ttl=None): - if value is None: - return - key = self.key_name(key) - value = json.dumps(value) - self.redis.set(key, value, ex=ttl) - - def delete(self, key): - key = self.key_name(key) - self.redis.delete(key) diff --git a/sg_wechat_enterprise/we_api/session/shovestorage.py b/sg_wechat_enterprise/we_api/session/shovestorage.py deleted file mode 100644 index 7742d71c..00000000 --- a/sg_wechat_enterprise/we_api/session/shovestorage.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals -from wechatpy.session import SessionStorage - - -class ShoveStorage(SessionStorage): - - def __init__(self, shove, prefix='wechatpy'): - self.shove = shove - self.prefix = prefix - - def key_name(self, key): - return '{0}:{1}'.format(self.prefix, key) - - def get(self, key, default=None): - key = self.key_name(key) - try: - return self.shove[key] - except KeyError: - return default - - def set(self, key, value, ttl=None): - if value is None: - return - - key = self.key_name(key) - self.shove[key] = value - - def delete(self, key): - key = self.key_name(key) - try: - del self.shove[key] - except KeyError: - pass diff --git a/sg_wechat_enterprise/we_api/utils.py b/sg_wechat_enterprise/we_api/utils.py deleted file mode 100644 index 7e54e1a3..00000000 --- a/sg_wechat_enterprise/we_api/utils.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -""" - wechatpy.utils - ~~~~~~~~~~~~~~~ - - This module provides some useful utilities. - - :copyright: (c) 2014 by messense. - :license: MIT, see LICENSE for more details. -""" -from __future__ import absolute_import, unicode_literals -import six -import six.moves.urllib.parse as urlparse -import sys -import string -import random -import hashlib - -try: - '''Use simplejson if we can, fallback to json otherwise.''' - import simplejson as json -except ImportError: - import json # NOQA - - -class ObjectDict(dict): - """Makes a dictionary behave like an object, with attribute-style access. - """ - - def __getattr__(self, key): - if key in self: - return self[key] - return None - - def __setattr__(self, key, value): - self[key] = value - - -class WeChatSigner(object): - """WeChat data signer""" - - def __init__(self, delimiter=b''): - self._data = [] - self._delimiter = to_binary(delimiter) - - def add_data(self, *args): - """Add data to signer""" - for data in args: - self._data.append(to_binary(data)) - - @property - def signature(self): - """Get data signature""" - self._data.sort() - str_to_sign = self._delimiter.join(self._data) - return hashlib.sha1(str_to_sign).hexdigest() - - -def check_signature(token, signature, timestamp, nonce): - """Check WeChat callback signature, raises InvalidSignatureException - if check failed. - - :param token: WeChat callback token - :param signature: WeChat callback signature sent by WeChat server - :param timestamp: WeChat callback timestamp sent by WeChat server - :param nonce: WeChat callback nonce sent by WeChat sever - """ - signer = WeChatSigner() - signer.add_data(token, timestamp, nonce) - if signer.signature != signature: - from wechatpy.exceptions import InvalidSignatureException - - raise InvalidSignatureException() - - -def to_text(value, encoding='utf-8'): - """Convert value to unicode, default encoding is utf-8 - - :param value: Value to be converted - :param encoding: Desired encoding - """ - if not value: - return '' - if isinstance(value, six.text_type): - return value - if isinstance(value, six.binary_type): - return value.decode(encoding) - return six.text_type(value) - - -def to_binary(value, encoding='utf-8'): - """Convert value to binary string, default encoding is utf-8 - - :param value: Value to be converted - :param encoding: Desired encoding - """ - if not value: - return b'' - if isinstance(value, six.binary_type): - return value - if isinstance(value, six.text_type): - return value.encode(encoding) - return six.binary_type(value) - - -def timezone(zone): - """Try to get timezone using pytz or python-dateutil - - :param zone: timezone str - :return: timezone tzinfo or None - """ - try: - import pytz - return pytz.timezone(zone) - except ImportError: - pass - try: - from dateutil.tz import gettz - return gettz(zone) - except ImportError: - return None - - -def random_string(length=16): - rule = string.ascii_letters + string.digits - rand_list = random.sample(rule, length) - return ''.join(rand_list) - - -def get_querystring(uri): - """Get Qeruystring information from uri. - - :param uri: uri - :return: querystring info or {} - """ - parts = urlparse.urlsplit(uri) - if sys.version_info[:2] == (2, 6): - query = parts.path - if query.startswith('?'): - query = query[1:] - else: - query = parts.query - return urlparse.parse_qs(query) - - -def byte2int(c): - if six.PY2: - return ord(c) - return c