# -*- coding: utf-8 -*-
from .exceptions import WegoApiError, WeChatUserError
from functools import reduce
import wego
import json
import time
import random
import string
import hashlib
[docs]class WegoApi(object):
"""
Wego api dead simple for humans.
"""
def __init__(self, settings):
self.settings = settings
self.wechat = wego.WeChatApi(settings)
[docs] def login_required(self, func):
"""
Decorator:use for request function, and it will init an independent WegoApi instance.
"""
def get_wx_user(request, *args, **kwargs):
"""
Called by login_required, it will set some attributes to function`s first param.
:param request: Function`s first param.
:return: Subject to availability.
"""
helper = self.settings.HELPER(request)
code = helper.get_params().get('code', '')
openid = None
if code:
openid = self.get_openid(helper, code)
helper.set_session('wx_openid', openid)
if not openid:
openid = helper.get_session('wx_openid')
if openid:
request.wego = self
request.wx_openid = openid
wx_user = self.get_userinfo(helper, openid)
if wx_user != 'error':
request.wx_user = wx_user
return func(request, *args, **kwargs)
return self.redirect_for_code(helper)
return get_wx_user
[docs] def redirect_for_code(self, helper):
"""
Let user jump to wechat authorization page.
:return: Redirect object
"""
state = 'WEGO'
if self.settings.REDIRECT_PATH:
redirect_url = self.settings.REDIRECT_PATH
if self.settings.REDIRECT_STATE:
state = self.settings.REDIRECT_STATE
else:
redirect_url = helper.get_current_path()
get_params = helper.get_params()
if 'code' not in get_params:
state = '&'.join(['%s=%s' % (i, j) for i, j in get_params.items()])
url = self.wechat.get_code_url(redirect_url, state)
return helper.redirect(url)
[docs] def get_openid(self, helper, code):
"""
Get user openid.
:param code: A code that user redirect back will bring.
:return: openid
"""
data = self.wechat.get_access_token(code)
self._set_user_tokens(helper, data)
return data['openid']
[docs] def get_userinfo(self, helper, openid):
"""
Get user info.
:return: :class:`WeChatUser <wego.api.WeChatUser>` object
"""
wechat_user = self._get_userinfo_from_session(helper)
if wechat_user:
return wechat_user
wx_access_token_expires_at = helper.get_session('wx_access_token_expires_at')
if wx_access_token_expires_at and float(wx_access_token_expires_at) < time.time():
refresh_token = helper.get_session('wx_refresh_token')
new_token = self.wechat.refresh_access_token(refresh_token)
if new_token == 'error':
return 'error'
self._set_user_tokens(helper, new_token)
access_token = helper.get_session('wx_access_token')
data = self.wechat.get_userinfo_by_token(openid, access_token)
self._set_userinfo_to_session(helper, data)
return WeChatUser(self, data)
def _get_userinfo_from_session(self, helper):
"""
Get user info from session.
:return: None or :class:`WeChatUser <wego.api.WeChatUser>` object
"""
if self.settings.USERINFO_EXPIRE:
wx_userinfo = helper.get_session('wx_userinfo')
if wx_userinfo:
wx_userinfo = dict({'expires_at': 0}, **json.loads(wx_userinfo))
if wx_userinfo['expires_at'] > time.time():
return WeChatUser(self, wx_userinfo)
return None
def _set_userinfo_to_session(self, helper, data):
"""
Set user info into session.
:param data: user info.
:return: None
"""
data['expires_at'] = time.time() + self.settings.USERINFO_EXPIRE
helper.set_session('wx_userinfo', json.dumps(data))
def _set_user_tokens(self, helper, data):
"""
Set user all tokens to sessions.
:param data: Tokens.
:return: None
"""
helper.set_session('wx_access_token', data['access_token'])
helper.set_session('wx_access_token_expires_at', str(time.time() + data['expires_in'] - 180))
helper.set_session('wx_refresh_token', data['refresh_token'])
[docs] def get_ext_userinfo(self, openid):
"""
Get user extra info, such as subscribe, language, remark and groupid.
:return: :dict: User data
"""
data = self.wechat.get_userinfo(openid)
return WeChatUser(self, data)
[docs] def verification_token(self, openid, access_token):
"""
Determine whether the user access token has expired
:param openid: User openid.
:param access_token: function get_access_token returns.
:return: Bool.
"""
data = self.wechat.is_access_token_has_expired(openid, access_token)
return data['errmsg'] == 'ok'
# TODO 看看是否有必要放入一个新的类里面
# 统一下单
[docs] def unified_order(self, **kwargs):
"""
# TODO
Unifiedorder settings, get wechat config at https://api.mch.weixin.qq.com/pay/unifiedorder
You can take return value as wechat api onBridgeReady's parameters directly
You don't need to include appid, mch_id, nonce_str and sign because these three parameters set by WeChatApi,
but the following parameters are necessary, you must be included in the kwargs
and you must follow the format below as the parameters's key
:param openid: User openid.
:param body: Goods are simply described, the field must be in strict accordance with the
specification, specific see parameters
:param out_trade_no: Merchants system internal order number, within 32 characters,
can include letters, other see merchant order number
:param total_fee: Total amount of orders, the unit for points, as shown in the payment amount
:param spbill_create_ip: APP and web payment submitted to client IP, Native fill call
WeChat payment API machine IP.
:param notify_url: (optional) Default is what you set at init.
Receive pay WeChat asynchronous notification callback address,
notify the url must be accessible url directly, cannot carry parameters.
:param trade_type: Values are as follows: the JSAPI, NATIVE APP, details see parameter regulation
:return: {'appId': string,
'timeStamp': value,
'nonceStr': value,
'package': value,
'signType': value,
'paySign': value,}
"""
default_data = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
'notify_url': self.settings.PAY_NOTIFY_URL,
'trade_type': 'JSAPI',
}
data = dict(default_data, **kwargs)
if self.settings.DEBUG:
data['total_fee'] = 1
data['sign'] = self.make_sign(data)
self._check_params(
data,
'appid',
'mch_id',
'nonce_str',
'body',
'out_trade_no',
'total_fee',
'spbill_create_ip',
'notify_url',
'trade_type')
order_info = self.wechat.unified_order(data)
if 'result_code' not in order_info or order_info['result_code'] != 'SUCCESS':
return self.settings.LOGGER.warn(u'统一下单失败! \n传入数据:\n{}\n返回数据:\n{}'.format(
json.dumps(data, indent=2),
json.dumps(order_info, indent=2)
))
data = {
'appId': order_info['appid'],
'timeStamp': str(int(time.time())),
'nonceStr': order_info['nonce_str'],
'package': 'prepay_id=' + order_info['prepay_id'],
'signType': 'MD5'
}
data['paySign'] = self.make_sign(data)
return data
# 查询订单
[docs] def query_order(self, out_trade_no=None, transaction_id=None):
"""
# TODO https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
Order query setting, get wechat config at https://api.mch.weixin.qq.com/pay/orderquery
Choose one in out_trade_no and transaction_id as parameter pass to this function
:param out_trade_no | transaction_id: WeChat order number, priority in use.
Merchants system internal order number, when didn't provide transaction_id need to pass this.
:return: {...}
"""
default_settings = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
}
if out_trade_no:
default_settings['out_trade_no'] = out_trade_no
elif transaction_id:
default_settings['transaction_id'] = transaction_id
else:
raise WegoApiError('Missing required parameters "{param}" (缺少必须的参数 "{param}")'.format(
param='out_trade_no|transaction_id'
))
default_settings['sign'] = self.make_sign(default_settings)
data = self.wechat.query_order(default_settings)
return data
# 关闭订单
[docs] def close_order(self, out_trade_no):
"""
# TODO return bool?
Close order, get wechat config at https://api.mch.weixin.qq.com/pay/closeorder
:param out_trade_no: Merchant order number within the system
:return: {...}
"""
data = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
'out_trade_no': out_trade_no,
}
data['sign'] = self.make_sign(data)
data = self.wechat.close_order(data)
return data
# 申请退款
[docs] def refund_order(self, **kwargs):
"""
Merchant order number within the system, get wechat config at https://api.mch.weixin.qq.com/secapi/pay/refund
Following parameters are necessary, you must be included in the kwargs and you must follow the format below as the parameters's key
:param out_trade_no or transaction_id: WeChat order number, priority in use. Merchants system internal order number, when didn't provide transaction_id need to pass this.
:param out_refund_no: Merchants system within the refund number,
merchants within the system, only the same refund order request only a back many times
:param total_fee: Total amount of orders, the unit for points, only as an integer, see the payment amount
:param refund_fee: Refund the total amount, total amount of the order,
the unit for points, only as an integer, see the payment amount
:param op_user_id: Operator account, the default for the merchants
:return: {...}
"""
default_settings = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
}
if 'op_user_id' not in kwargs:
kwargs['op_user_id'] = self.settings.MCH_ID
data = dict(default_settings, **kwargs)
if self.settings.DEBUG:
data['total_fee'] = 1
data['sign'] = self.make_sign(data)
self._check_params(
data,
'appid',
'mch_id',
'nonce_str',
'sign',
'out_refund_no',
'total_fee',
'refund_fee',
'op_user_id'
)
if 'out_trade_no' not in kwargs and 'transaction_id' not in kwargs:
raise WegoApiError('Missing required parameters "{param}" (缺少必须的参数 "{param}")'.format(
param='out_trade_on|transaction_id'
))
data = self.wechat.refund_order(data)
return data
# 查询退款
[docs] def query_refund(self, **kwargs):
"""
get wechat config at https://api.mch.weixin.qq.com/pay/refundquery
:param transaction_id | out_trade_no | out_refund_no | refund_id: One out of four
:return: dict {...}
"""
default_settings = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
}
keys = ['transaction_id', 'out_trade_no', 'out_refund_no', 'refund_id']
for i in keys:
if i in kwargs:
break
else:
raise WegoApiError('Missing required parameters "{param}" (缺少必须的参数 "{param}")'.format(
param='out_trade_on|transaction_id|out_refund_no|refund_id'
))
data = dict(default_settings, **kwargs)
data['sign'] = self.make_sign(data)
data = self.wechat.query_refund(data)
return data
# 下载对账单
[docs] def download_bill(self, **kwargs):
"""
get wechat config at https://api.mch.weixin.qq.com/pay/downloadbill
:param bill_date:
:param bill_type:
:return: dict {...}
"""
default_settings = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
}
data = dict(default_settings, **kwargs)
data['sign'] = self.make_sign(data)
self._check_params(
data,
'appid',
'mch_id',
'nonce_str',
'sign',
'bill_date',
'bill_type')
data = self.wechat.download_bill(data)
return data
# TODO 暂时无需
# 交易保障
[docs] def pay_report(self, **kwargs):
"""
get wechat config at https://api.mch.weixin.qq.com/payitil/report
:param interface_url:
:param execute_time:
:param return_code:
:param result_code:
:param user_ip:
:return: dict{...}
"""
default_settings = {
'appid': self.settings.APP_ID,
'mch_id': self.settings.MCH_ID,
'nonce_str': self._get_random_code(),
}
data = dict(default_settings, **kwargs)
data['sign'] = self.make_sign(data)
self._check_params(
data,
'appid',
'mch_id',
'nonce_str',
'sign',
'interface_url',
'execute_time',
'return_code',
'result_code',
'user_ip'
)
data = self.wechat.pay_report(data)
return data
def _check_params(self, params, *args):
"""
Check if params is available
:param params: a dict.
:return: None
"""
for i in args:
if i not in params or not params[i]:
raise WegoApiError('Missing required parameters "{param}" (缺少必须的参数 "{param}")'.format(param=i))
def _get_random_code(self):
"""
Get random code
"""
return reduce(lambda x, y: x + y, [random.choice(string.printable[:62]) for i in range(32)])
[docs] def make_sign(self, data):
"""
Generate wechat pay for signature
"""
temp = ['%s=%s' % (k, data[k]) for k in sorted(data.keys())]
temp.append('key=' + self.settings.MCH_SECRET)
temp = '&'.join(temp)
md5 = hashlib.md5()
md5.update(temp.encode('utf-8'))
return md5.hexdigest().upper()
[docs] def create_group(self, name):
"""
Create a new group.
:param name: Group name.
:return: :dict: {'id': 'int', 'name':'str'}
"""
return self.wechat.create_group(name)['group']
[docs] def get_groups(self):
"""
Get all groups.
:return: :dict: {'your_group_id': {'name':'str', 'count':'int'}}
"""
data = self.wechat.get_all_groups()
return {i.pop('id'): i for i in data['groups']}
[docs] def get_user_groups(self, openid):
"""
Get user groups.
:return: :dict: {'your_group_id': {'name':'str', 'count':'int'}}
"""
data = self.wechat.get_user_groups(openid)
return data
def _get_groupid(self, group):
"""
Input group id or group name and return group id.
:param group: Group name or group id.
:return: group id
"""
groups = self.get_groups()
if type(group) is int:
groupid = int(group)
else:
group = str(group)
for i in groups:
if groups[i]['name'] == group:
groupid = i
break
else:
raise WegoApiError(u'Without this group(没有这个群组)')
if groupid not in groups:
raise WegoApiError(u'Without this group(没有这个群组)')
return groupid
[docs] def change_group_name(self, group, name):
"""
Change group name.
:param group: Group id or group name.
:param name: New group name
:return: :Bool
"""
groupid = self._get_groupid(group)
data = self.wechat.change_group_name(groupid, name)
return not data['errcode']
[docs] def change_user_group(self, openid, group):
"""
Change user group.
:param group: Group id or group name.
:return: :Bool .
"""
groupid = self._get_groupid(group)
data = self.wechat.change_user_group(openid, groupid)
return not data['errcode']
[docs] def del_group(self, group):
"""
Delete group.
:param group: Group id or group name.
:return: :Bool
"""
groupid = self._get_groupid(group)
data = self.wechat.del_group(groupid)
return not data['errcode']
def get_menus(self):
data = self.wechat.get_menus()
if 'errcode' in data and data['errcode'] == 46003:
return {'menu': {}}
return data
def del_menu(self, target='all'):
if target == 'all':
return not self.wechat.del_all_menus()['errcode']
return not self.wechat.del_conditional_menu(int(target))['errcode']
[docs] def analysis_push(self, request):
"""
Analysis xml to dict and set wego push type.
Wego defind WeChatPush type (which can reply has checked):
-- pay --
all ✓
-- msg --
text ✓
image ✓
voice ✓
video ✓
shortvideo ✓
location ✓
link ✓
-- event --
subscribe ✓'
unsubscribe
scancode_push
scancode_waitmsg ✓
scan
scan_subscribe
user_location ✓
click ✓
view
:param raw_xml: Raw xml.
:return: :class:`WeChatPush <wego.api.WeChatPush>` object.
:rtype: WeChatPush.
"""
helper = self.settings.HELPER(request)
raw_xml = helper.get_body()
if raw_xml.find('return_code') != -1:
# TODO 通知验证
data = self.wechat._analysis_xml(raw_xml)
return WeChatPay(data)
crypto = None
nonce = None
if self.settings.PUSH_TOKEN:
if not hasattr(self, 'push_crypto'):
from .lib.WEGOBizMsgCrypt import WXBizMsgCrypt
self.push_crypto = WXBizMsgCrypt(
self.settings.PUSH_TOKEN,
self.settings.PUSH_ENCODING_AES_KEY,
self.settings.APP_ID
)
crypto = self.push_crypto
msg_sign = helper.get_params()['msg_signature']
timestamp = helper.get_params()['timestamp']
nonce = helper.get_params()['nonce']
ret, raw_xml = self.push_crypto.DecryptMsg(raw_xml, msg_sign, timestamp, nonce)
data = self.wechat._analysis_xml(raw_xml)
return WeChatPush(data, crypto, nonce)
def add_temporary_material(self, **kwargs):
data = self.wechat.add_temporary_material(**kwargs)
return data
def get_temporary_material(self, media_id):
data = self.wechat.get_temporary_material(media_id)
return data
def add_permanent_material(self, articles):
data = self.wechat.add_permanent_material(articles)
return data
def upload_content_picture(self, media):
data = self.wechat.upload_content_picture(media)
return data
def add_other_material(self, **kwargs):
data = self.wechat.add_other_material(**kwargs)
return data
def get_permanent_material(self, media_id):
data = self.wechat.get_permanent_material(media_id)
return data
def delete_material(self, media_id):
data = self.wechat.delete_material(media_id)
return data
def update_material(self, **kwargs):
data = self.wechat.update_material(**kwargs)
return data
def get_materials_count(self):
data = self.wechat.get_materials_count()
return data
def get_materials_list(self, material_type, offset, count):
data = self.wechat.get_materials_list(material_type, offset, count)
return data
def create_qrcode(self, key, expire=None):
if expire:
data = self.wechat.create_scene_qrcode(key, expire)
elif type(key) is str:
data = self.wechat.create_limit_scene_qrcode(key)
else:
data = self.wechat.create_limit_str_scene_qrcode(key)
# TODO 容错
data['code_url'] = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' + data['ticket']
return data
def create_short_url(self, url):
data = self.wechat.create_short_url(url)
# TODO 容错
return data['short_url']
[docs] def get_wechat_servers_list(self):
"""
Get wechat servers list
:return: :list
"""
data = self.wechat.get_wechat_servers_list()
return data
[docs] def get_variation_number_of_user(self, begin_date, end_date):
"""
Get Variation on number of user
:param data:begin_date, end_date
:return: :dict
"""
data = self.wechat.get_variation_number_of_user(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_user_cumulate(self, begin_date, end_date):
"""
GET accumulation of user
:param date:begin_date, end_date
:return: :dict
"""
data = self.wechat.get_user_cumulate(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_article_summary(self, begin_date, end_date):
"""
Get article summary
:param date:begin_date, end_date
:return: :dict
"""
data = self.wechat.get_article_summary(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_article_total(self, begin_date, end_date):
"""
Get article total
:param data: begin_date, end_date
:return: :dict
"""
data = self.wechat.get_article_total(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_user_read(self, begin_date, end_date):
"""
Get user read
:param data:begin_date, end_date
:return : :dict
"""
data = self.wechat.get_user_read(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_user_read_hour(self, begin_date, end_date):
"""
Get user read hour
param data:begin_date, end_date
return : :dict
"""
data = self.wechat.get_user_read_hour(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_user_share(self, begin_date, end_date):
"""
Get user share
param data:begin_data,end_date
return : :dict
"""
data = self.wechat.get_user_share(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
[docs] def get_user_share_hour(self, begin_date, end_date):
"""
Get user share
param data:begin_date, end_date
retur : :dict
"""
data = self.wechat.get_user_share_hour(begin_date, end_date)
if 'errcode' in data:
if data['errcode'] == 61501:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数跨度异常。)')
elif data['errcode'] == 61500:
raise WegoApiError(data['errmsg'] + u'(错误返回码:' + str(data['errcode']) + u',时间参数格式异常。)')
return data
class WeChatPay(object):
def __init__(self, data):
self.is_pay = True
self.data = data
self.return_tpl = ('<xml>' +
'<return_code><![CDATA[SUCCESS]]></return_code>' +
'<return_msg><![CDATA[%s]]></return_msg>' +
'</xml>')
self.success = self.return_tpl % 'OK'
def fail(self, text):
return self.return_tpl % text
def __getattr__(self, key):
if key in self.data:
return self.data[key]
return ''
[docs]class WeChatPush(object):
"""
"""
def __init__(self, data, crypto=None, nonce=None):
self.data = data
self.crypto = crypto
self.nonce = nonce
if data['MsgType'] == 'event':
if data['Event'] == 'subscribe' and 'Ticket' in data:
self.type = 'scan_subcribe'
elif data['Event'] == 'LOCATION':
self.type = 'user_location'
else:
self.type = data['Event'].lower()
else:
self.type = data['MsgType']
self.from_user = data['FromUserName']
self.to_user = data['ToUserName']
def return_xml(self, data):
data['ToUserName'] = self.from_user
data['FromUserName'] = self.to_user
data['CreateTime'] = int(time.time())
xml = wego.wechat.WeChatApi._make_xml(data)
if self.nonce:
ret, xml = self.crypto.EncryptMsg(xml, self.nonce)
return xml
def __getattr__(self, key):
if key in self.data:
return self.data[key]
return ''
def reply_text(self, text):
return self.return_xml({
'MsgType': 'text',
'Content': text
})
def reply_image(self, image):
return self.return_xml({
'MsgType': 'image',
'Image': {'MediaId': image}
})
def reply_voice(self, voice):
return self.return_xml({
'MsgType': 'voice',
'Voice': {'MediaId': voice}
})
def reply_video(self, video):
# TODO 视频要等审核通过才能用, 或者是永久素材
data = {
'MediaId': video['media_id']
}
if 'title' in video:
data['Title'] = video['title']
if 'description' in video:
data['Description'] = video['description']
return self.return_xml({
'MsgType': 'video',
'Video': data
})
def reply_music(self, music):
data = {
'Title': music['title'],
'Description': music['description'],
'MusicUrl': music['music_url'],
'HQMusicUrl': music['hq_music_url'],
}
if 'thumb_media_id' in music:
data['ThumbMediaId'] = music['thumb_media_id']
return self.return_xml({
'MsgType': 'music',
'Music': data
})
def reply_news(self, news):
data = []
for i in news:
new_dict = {}
if 'title' in i:
new_dict['Title'] = i['title']
if 'description' in i:
new_dict['Description'] = i['description']
if 'pic_url' in i:
new_dict['PicUrl'] = i['pic_url']
if 'url' in i:
new_dict['Url'] = i['url']
data.append(new_dict)
return self.return_xml({
'MsgType': 'news',
'ArticleCount': len(news),
'Articles': {
'item': data
}
})
[docs]class WeChatUser(object):
"""
A lazy and smart wechat user object. You can set user remark, group, groupid direct,
because of group name can be repeated, so if you set the group by group name, it may not be accurate.
"""
def __init__(self, wego, data):
self.wego = wego
self.data = data
self.is_upgrade = False
def __getattr__(self, key):
ext_userinfo = ['subscribe', 'language', 'remark', 'groupid']
if key in ext_userinfo and not self.is_upgrade:
self.get_ext_userinfo()
if key == 'group' and key not in self.data:
self.data['group'] = self.wego.get_groups()[self.groupid]
if key in self.data:
return self.data[key]
return ''
def __setattr__(self, key, value):
if key == 'remark':
if self.subscribe != 1:
raise WeChatUserError('The user does not subscribe you')
if self.data['remark'] != value:
self.wego.wechat.set_user_remark(self.data['openid'], value)
self.data[key] = value
if key in ['group', 'groupid']:
groups = self.wego.get_groups()
if key == 'group':
for i in groups:
if groups[i]['name'] == value:
value = i
break
else:
raise WeChatUserError(u'Without this group(没有这个群组)')
groupid = value
if groupid not in groups:
raise WeChatUserError(u'Without this group(没有这个群组)')
self.wego.change_user_group(self.data['openid'], groupid)
super(WeChatUser, self).__setattr__(key, value)
[docs] def get_ext_userinfo(self):
"""
Get user extra info, such as subscribe, language, remark and groupid.
:return: :dict: User data
"""
self.data['remark'] = ''
self.data['groupid'] = ''
data = self.wego.wechat.get_userinfo(self.data['openid'])
self.data = dict(self.data, **data)
self.is_upgrade = True
return self.data
# TODO 更方便定制
[docs]def official_get_global_access_token(self):
"""
Get global access token.
:param self: Call self.get_global_access_token() for get global access token.
:return: :str: Global access token
"""
if not self.global_access_token or self.global_access_token['expires_at'] <= int(time.time()):
self.global_access_token = self.get_global_access_token()
self.global_access_token['expires_at'] = self.global_access_token['expires_in'] + int(time.time()) - 180
return self.global_access_token['access_token']