__init__.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. from __future__ import absolute_import, unicode_literals
  4. import logging
  5. import random
  6. import string
  7. import time
  8. import requests
  9. from library.wechatpy.client import api
  10. from library.wechatpy.client.base import BaseWeChatClient
  11. from library.wechatpy.utils import WeChatSigner
  12. from library.wechatbase.exceptions import WeChatException, WechatNetworkException
  13. logger = logging.getLogger(__name__)
  14. class WeChatClient(BaseWeChatClient):
  15. """
  16. 微信 API 操作类
  17. 通过这个类可以操作微信 API,发送主动消息、群发消息和创建自定义菜单等。
  18. """
  19. API_BASE_URL = 'https://api.weixin.qq.com/cgi-bin/'
  20. card = api.WeChatCard()
  21. customservice = api.WeChatCustomService()
  22. datacube = api.WeChatDataCube()
  23. device = api.WeChatDevice()
  24. group = api.WeChatGroup()
  25. invoice = api.WeChatInvoice()
  26. material = api.WeChatMaterial()
  27. media = api.WeChatMedia()
  28. menu = api.WeChatMenu()
  29. merchant = api.WeChatMerchant()
  30. message = api.WeChatMessage()
  31. misc = api.WeChatMisc()
  32. poi = api.WeChatPoi()
  33. qrcode = api.WeChatQRCode()
  34. scan = api.WeChatScan()
  35. semantic = api.WeChatSemantic()
  36. shakearound = api.WeChatShakeAround()
  37. tag = api.WeChatTag()
  38. template = api.WeChatTemplate()
  39. subscribe = api.WeChatNewtmpl()
  40. user = api.WeChatUser()
  41. wifi = api.WeChatWiFi()
  42. wxa = api.WeChatWxa()
  43. marketing = api.WeChatMarketing()
  44. def __init__(self, appid, secret, session, timeout=None, auto_retry=True):
  45. super(WeChatClient, self).__init__(
  46. appid, session, timeout, auto_retry
  47. )
  48. self.appid = appid
  49. self.secret = secret
  50. @property
  51. def nonce_str(self):
  52. char = string.ascii_letters + string.digits
  53. return "".join(random.choice(char) for _ in range(32))
  54. def jsapi_sign(self, url):
  55. """
  56. 生成签名给js使用
  57. """
  58. jsapi_ticket = self.jsapi_ticket
  59. timestamp = str(int(time.time()))
  60. nonce_str = self.nonce_str
  61. data = [
  62. 'noncestr={noncestr}'.format(noncestr=nonce_str),
  63. 'jsapi_ticket={jsapi_ticket}'.format(jsapi_ticket=jsapi_ticket),
  64. 'timestamp={timestamp}'.format(timestamp=timestamp),
  65. 'url={url}'.format(url=url),
  66. ]
  67. signer = WeChatSigner(delimiter=b'&')
  68. signer.add_data(*data)
  69. return {
  70. 'sign': signer.signature,
  71. 'timestamp': timestamp,
  72. 'noncestr': nonce_str,
  73. 'appId': self.appid,
  74. 'jsapi_ticket': jsapi_ticket
  75. }
  76. def refresh_jsapi_ticket(self):
  77. """
  78. 获取微信 JS-SDK ticket
  79. :return: 返回的 JSON 数据包
  80. """
  81. jsapi_ticket_response = self.get(
  82. 'ticket/getticket',
  83. params={'type': 'jsapi'}
  84. )
  85. ticket = jsapi_ticket_response['ticket']
  86. expires_in = int(jsapi_ticket_response['expires_in'])
  87. if expires_in < 600:
  88. expires_in = expires_in / 2
  89. else:
  90. expires_in = expires_in - 600
  91. self.session.set(self.jsapi_ticket_key, ticket, expires_in)
  92. return ticket
  93. def refresh_access_token(self):
  94. logger.info('Fetching access token for {}'.format(self.appid))
  95. with requests.sessions.Session() as _session:
  96. res = _session.get(
  97. url='https://api.weixin.qq.com/cgi-bin/token',
  98. params={
  99. 'grant_type': 'client_credential',
  100. 'appid': self.appid,
  101. 'secret': self.secret
  102. },
  103. timeout=self.timeout)
  104. try:
  105. res.raise_for_status()
  106. except requests.RequestException as reqe:
  107. raise WechatNetworkException(
  108. errCode='HTTP{}'.format(res.status_code),
  109. errMsg=reqe.message,
  110. client=self,
  111. request=reqe.request,
  112. response=reqe.response
  113. )
  114. result = res.json()
  115. if 'errcode' in result and result['errcode'] != 0:
  116. raise WeChatException(
  117. errCode=result['errcode'],
  118. errMsg=result['errmsg'],
  119. client=self,
  120. request=res.request,
  121. response=res
  122. )
  123. expires_in = 7200 - 600
  124. if 'expires_in' in result:
  125. expires_in = result['expires_in']
  126. if expires_in < 600:
  127. expires_in = expires_in / 2
  128. else:
  129. expires_in = expires_in - 600
  130. self.session.set(
  131. self.access_token_key,
  132. result['access_token'],
  133. expires_in
  134. )
  135. return result['access_token']
  136. class WeChatComponentClient(WeChatClient):
  137. """
  138. 开放平台代公众号调用客户端
  139. """
  140. def __init__(self, appid, component, session=None, timeout=None):
  141. # 未用到secret,所以这里没有
  142. super(WeChatComponentClient, self).__init__(appid, "", session, timeout)
  143. self.appid = appid
  144. self.component = component
  145. def refresh_access_token(self):
  146. """
  147. 获取 access token
  148. 详情请参考 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list\
  149. &t=resource/res_list&verify=1&id=open1419318587&token=&lang=zh_CN
  150. :return: 返回的 JSON 数据包
  151. """
  152. result = self.component.refresh_authorizer_token(self.appid)
  153. expires_in = 7200 - 600
  154. if 'expires_in' in result:
  155. expires_in = result['expires_in']
  156. if expires_in < 600:
  157. expires_in = expires_in / 2
  158. else:
  159. expires_in = expires_in - 600
  160. self.session.set(
  161. self.access_token_key,
  162. result['authorizer_access_token'],
  163. expires_in
  164. )
  165. return result['authorizer_access_token']