component.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. wechatpy.component
  5. ~~~~~~~~~~~~~~~
  6. This module provides client library for WeChat Open Platform
  7. :copyright: (c) 2015 by hunter007.
  8. :license: MIT, see LICENSE for more details.
  9. """
  10. import json
  11. import logging
  12. import time
  13. from urllib import quote
  14. import requests
  15. import xmltodict
  16. from library import to_text, my_memcache_lock
  17. from library.wechatpy import access_token_key, component_verify_ticket_key, refresh_token_key
  18. from library.wechatpy.client import WeChatComponentClient
  19. from library.wechatpy.constants import WeChatErrorCode
  20. from library.wechatpy.crypto import WeChatCrypto
  21. from library.wechatbase.exceptions import (
  22. APILimitedException,
  23. WeChatException,
  24. WeChatComponentOAuthException,
  25. WeChatOAuthException, WechatNetworkException
  26. )
  27. from library.wechatpy.messages import COMPONENT_MESSAGE_TYPES, ComponentUnknownMessage
  28. from library.wechatpy.parser import parse_message
  29. logger = logging.getLogger(__name__)
  30. NO_RETRY_ERRCODE = [
  31. '48001', # api unauthorized
  32. '40164' # invalid ip not in whitelist
  33. '61004' # access clientip is not registered request
  34. ]
  35. class BaseWeChatComponent(object):
  36. API_BASE_URL = "https://api.weixin.qq.com/cgi-bin"
  37. def __init__(
  38. self,
  39. component_appid,
  40. component_appsecret,
  41. component_token,
  42. encoding_aes_key,
  43. lock_cache,
  44. session,
  45. authorizer,
  46. auto_retry=True,
  47. ):
  48. """
  49. :param component_appid: 第三方平台appid
  50. :param component_appsecret: 第三方平台appsecret
  51. :param component_token: 公众号消息校验Token
  52. :param encoding_aes_key: 公众号消息加解密Key
  53. """
  54. self._http = requests.Session()
  55. self.component_appid = component_appid
  56. self.component_appsecret = component_appsecret
  57. self.crypto = WeChatCrypto(component_token, encoding_aes_key, component_appid)
  58. self.session = session
  59. self.authorizer = authorizer
  60. self.lock_cache = lock_cache
  61. self.auto_retry = auto_retry
  62. @property
  63. def component_verify_ticket(self):
  64. return self.session.get(component_verify_ticket_key(self.component_appid))
  65. def _request(self, method, url_or_endpoint, **kwargs):
  66. if not url_or_endpoint.startswith(("http://", "https://")):
  67. api_base_url = kwargs.pop("api_base_url", self.API_BASE_URL)
  68. url = "{}{}".format(api_base_url, url_or_endpoint)
  69. else:
  70. url = url_or_endpoint
  71. if "params" not in kwargs:
  72. kwargs["params"] = {}
  73. if isinstance(kwargs["params"], dict) and "component_access_token" not in kwargs["params"]:
  74. kwargs["params"]["component_access_token"] = self.access_token
  75. if isinstance(kwargs["data"], dict):
  76. kwargs["data"] = json.dumps(kwargs["data"])
  77. res = self._http.request(method = method, url = url, **kwargs)
  78. try:
  79. res.raise_for_status()
  80. except requests.RequestException as reqe:
  81. raise WechatNetworkException(
  82. errCode = 'HTTP{}'.format(res.status_code),
  83. errMsg = reqe.message,
  84. client = self,
  85. request = reqe.request,
  86. response = reqe.response)
  87. return self._handle_result(res, method, url, **kwargs)
  88. def _handle_result(self, res, method=None, url=None, **kwargs):
  89. result = json.loads(res.content.decode("utf-8", "ignore"), strict=False)
  90. if "errcode" in result:
  91. result["errcode"] = int(result["errcode"])
  92. if "errcode" in result and result["errcode"] != 0:
  93. errcode = result["errcode"]
  94. errmsg = result.get("errmsg", errcode)
  95. if self.auto_retry and errcode in (
  96. WeChatErrorCode.INVALID_CREDENTIAL.value,
  97. WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
  98. WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value,
  99. ):
  100. logger.info("Component access token expired, fetch a new one and retry request")
  101. self.fetch_access_token()
  102. kwargs["params"]["component_access_token"] = self.session.get(
  103. "{}_component_access_token".format(self.component_appid)
  104. )
  105. return self._request(method=method, url_or_endpoint=url, **kwargs)
  106. elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
  107. # api freq out of limit
  108. raise APILimitedException(errcode, errmsg, client=self, request=res.request, response=res)
  109. else:
  110. raise WeChatException(errcode, errmsg, client=self, request=res.request, response=res)
  111. return result
  112. @property
  113. def component_access_token_key(self):
  114. return '{}_component_access_token'.format(self.component_appid)
  115. def fetch_access_token(self, old_token=None):
  116. """
  117. 获取 component_access_token
  118. 详情请参考 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list\
  119. &t=resource/res_list&verify=1&id=open1419318587&token=&lang=zh_CN
  120. :return: 返回的 JSON 数据包
  121. """
  122. key = 'component-access-token-lock-{appid}'.format(appid=self.component_appid)
  123. retry = 0
  124. while True:
  125. current_token = self.session.get(self.component_access_token_key)
  126. if current_token and current_token != old_token:
  127. logger.debug(
  128. '=== WechatToken === app<id={}> fetched one other component access token. access token = {}'.format(
  129. self.component_appid, current_token))
  130. return current_token
  131. with my_memcache_lock(self.lock_cache, key, '1', expire=15) as acquired:
  132. if acquired:
  133. try:
  134. new_token = self.refresh_access_token()
  135. if new_token:
  136. logger.debug(
  137. '=== WechatToken === app<id={}> fetch component access token success. access token = {}'.format(
  138. self.component_appid, new_token))
  139. return new_token
  140. else:
  141. raise Exception(
  142. '=== WechatToken === app<id={}> fetch component access token is null.'.format(
  143. self.component_appid))
  144. except APILimitedException as e:
  145. logger.error(repr(e))
  146. raise e
  147. except WeChatException as e:
  148. logger.exception(e)
  149. if str(e.errCode) in NO_RETRY_ERRCODE:
  150. raise e
  151. except Exception as e:
  152. logger.exception(e)
  153. else:
  154. logger.debug(
  155. '=== WechatToken === app<id={}> not acquire component access token memcache key<{}>'.format(
  156. self.component_appid, key))
  157. retry = retry + 1
  158. if retry >= 3:
  159. raise WeChatException(
  160. errCode=WeChatErrorCode.MY_SYSTEM_ERROR,
  161. errMsg='=== WechatToken === app<id={}> fetch component access token timeout.'.format(
  162. self.component_appid),
  163. client=self)
  164. time.sleep(5)
  165. def refresh_access_token(self):
  166. logger.info("Fetching component access token")
  167. url = "{}/component/api_component_token".format(self.API_BASE_URL)
  168. data = json.dumps(
  169. {
  170. "component_appid": self.component_appid,
  171. "component_appsecret": self.component_appsecret,
  172. "component_verify_ticket": self.component_verify_ticket,
  173. }
  174. )
  175. res = self._http.post(url=url, data=data)
  176. try:
  177. res.raise_for_status()
  178. except requests.RequestException as reqe:
  179. raise WechatNetworkException(
  180. errCode = 'HTTP{}'.format(res.status_code),
  181. errMsg = reqe.message,
  182. client = self,
  183. request = reqe.request,
  184. response = reqe.response)
  185. result = res.json()
  186. if "errcode" in result and result["errcode"] != 0:
  187. raise WeChatException(
  188. result["errcode"],
  189. result["errmsg"],
  190. client=self,
  191. request=res.request,
  192. response=res,
  193. )
  194. expires_in = 7200 - 600
  195. if 'expires_in' in result:
  196. expires_in = result['expires_in']
  197. if expires_in < 600:
  198. expires_in = expires_in / 2
  199. else:
  200. expires_in = expires_in - 600
  201. self.session.set(
  202. self.component_access_token_key,
  203. result['component_access_token'],
  204. expires_in
  205. )
  206. return result
  207. @property
  208. def access_token(self):
  209. """ WeChat access token """
  210. access_token = self.session.get(self.component_access_token_key)
  211. if access_token:
  212. return access_token
  213. else:
  214. return self.fetch_access_token()
  215. def get(self, url, **kwargs):
  216. return self._request(method="get", url_or_endpoint=url, **kwargs)
  217. def post(self, url, **kwargs):
  218. return self._request(method="post", url_or_endpoint=url, **kwargs)
  219. class WeChatComponent(BaseWeChatComponent):
  220. def get_pre_auth_url(self, redirect_uri):
  221. """
  222. 获取PC版授权链接
  223. """
  224. redirect_uri = quote(redirect_uri, safe=b"")
  225. url_template = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid={}&pre_auth_code={}&redirect_uri={}&auth_type='
  226. return url_template.format(self.component_appid, self.create_preauthcode()['pre_auth_code'], redirect_uri)
  227. def get_pre_auth_url_m(self, redirect_uri):
  228. """
  229. 获取H5版授权链接
  230. """
  231. redirect_uri = quote(redirect_uri, safe="")
  232. url_template = 'https://open.weixin.qq.com/wxaopen/safe/bindcomponent?action=bindcomponent&no_scan=1&component_appid={}&pre_auth_code={}&redirect_uri={}&auth_type=3#wechat_redirect'
  233. return url_template.format(self.component_appid, self.create_preauthcode()['pre_auth_code'], redirect_uri)
  234. def create_preauthcode(self):
  235. """
  236. 获取预授权码
  237. """
  238. return self.post(
  239. "/component/api_create_preauthcode",
  240. data={"component_appid": self.component_appid},
  241. )
  242. def query_auth(self, authorization_code):
  243. """
  244. 使用授权码换取公众号的授权信息
  245. :params authorization_code: 授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
  246. """
  247. result = self.post(
  248. "/component/api_query_auth",
  249. data={
  250. "component_appid": self.component_appid,
  251. "authorization_code": authorization_code,
  252. }
  253. )
  254. assert (result is not None and
  255. "authorization_info" in result and
  256. "authorizer_appid" in result["authorization_info"])
  257. return result
  258. def refresh_token(self, appid):
  259. refresh_token = self.session.get(refresh_token_key(appid))
  260. if not refresh_token:
  261. authorizer = self.authorizer.getAuthRecord(appid)
  262. if authorizer:
  263. refresh_token = authorizer.refreshToken
  264. self.session.set(refresh_token_key(appid), refresh_token, 7 * 24 * 3600)
  265. return refresh_token
  266. def refresh_authorizer_token(self, authorizer_appid):
  267. """
  268. 获取(刷新)授权公众号的令牌
  269. :params authorizer_appid: 授权方appid
  270. :params authorizer_refresh_token: 授权方的刷新令牌
  271. """
  272. return self.post(
  273. "/component/api_authorizer_token",
  274. data={
  275. "component_appid": self.component_appid,
  276. "authorizer_appid": authorizer_appid,
  277. "authorizer_refresh_token": self.refresh_token(authorizer_appid),
  278. },
  279. )
  280. def get_authorizer_info(self, authorizer_appid):
  281. """
  282. 获取授权方的账户信息
  283. :params authorizer_appid: 授权方appid
  284. """
  285. return self.post(
  286. "/component/api_get_authorizer_info",
  287. data={
  288. "component_appid": self.component_appid,
  289. "authorizer_appid": authorizer_appid,
  290. },
  291. )
  292. def get_authorizer_list(self, offset=0, count=500):
  293. """
  294. 拉取所有已授权的帐号信息
  295. :params offset: 偏移位置/起始位置
  296. :params count: 拉取数量
  297. """
  298. return self.post(
  299. "/component/api_get_authorizer_list",
  300. data={
  301. "component_appid": self.component_appid,
  302. "offset": offset,
  303. "count": count,
  304. },
  305. )
  306. def get_authorizer_option(self, authorizer_appid, option_name):
  307. """
  308. 获取授权方的选项设置信息
  309. :params authorizer_appid: 授权公众号appid
  310. :params option_name: 选项名称
  311. """
  312. return self.post(
  313. "/component/api_get_authorizer_option",
  314. data={
  315. "component_appid": self.component_appid,
  316. "authorizer_appid": authorizer_appid,
  317. "option_name": option_name,
  318. },
  319. )
  320. def set_authorizer_option(self, authorizer_appid, option_name, option_value):
  321. """
  322. 设置授权方的选项信息
  323. :params authorizer_appid: 授权公众号appid
  324. :params option_name: 选项名称
  325. :params option_value: 设置的选项值
  326. """
  327. return self.post(
  328. "/component/api_set_authorizer_option",
  329. data={
  330. "component_appid": self.component_appid,
  331. "authorizer_appid": authorizer_appid,
  332. "option_name": option_name,
  333. "option_value": option_value,
  334. },
  335. )
  336. def get_client_by_appid(self, authorizer_appid):
  337. """
  338. 通过 authorizer_appid 获取 Client 对象
  339. :params authorizer_appid: 授权公众号appid
  340. """
  341. access_token_key = "{}_access_token".format(authorizer_appid)
  342. access_token = self.session.get(access_token_key)
  343. if not access_token:
  344. ret = self.refresh_authorizer_token(authorizer_appid)
  345. access_token = ret["authorizer_access_token"]
  346. access_token_key = "{}_access_token".format(authorizer_appid)
  347. expires_in = 7200
  348. if "expires_in" in ret:
  349. expires_in = ret["expires_in"]
  350. self.session.set(access_token_key, access_token, expires_in)
  351. return WeChatComponentClient(authorizer_appid, self, session=self.session)
  352. def do_auth(self, auth_code):
  353. # 获取auth信息(authorizer_access_token, authorizer_refresh_token)
  354. auth_info = self.query_auth(auth_code)["authorization_info"]
  355. authorizer_appid = auth_info['authorizer_appid']
  356. authorizer_access_token = auth_info.get('authorizer_access_token', None)
  357. if authorizer_access_token:
  358. expires_in = 7200
  359. if "expires_in" in auth_info:
  360. expires_in = auth_info["expires_in"]
  361. self.session.set(access_token_key(authorizer_appid), authorizer_access_token, (expires_in - 600))
  362. payload = {
  363. 'appid': authorizer_appid,
  364. }
  365. authorizer_refresh_token = auth_info.get('authorizer_refresh_token', None)
  366. if authorizer_refresh_token:
  367. payload.update({'refreshToken': authorizer_refresh_token})
  368. self.session.set(refresh_token_key(authorizer_appid), authorizer_refresh_token, 7 * 24 * 3600)
  369. # 获取公众号或者小程序信息
  370. app_info = self.get_authorizer_info(authorizer_appid)
  371. authorizer_info = app_info['authorizer_info']
  372. payload.update({
  373. 'nickName': authorizer_info.pop('nick_name'),
  374. 'headImg': authorizer_info.pop('head_img'),
  375. 'userName': authorizer_info.pop('user_name'),
  376. 'principalName': authorizer_info.pop('principal_name'),
  377. 'qrcodeUrl': authorizer_info.pop('qrcode_url'),
  378. 'verifyInfo': authorizer_info.pop('verify_type_info')['id'],
  379. 'serviceType': authorizer_info.pop('service_type_info')['id'],
  380. 'appStatus': authorizer_info.pop('account_status')
  381. })
  382. payload['extra'] = authorizer_info
  383. if 'MiniProgramInfo' in authorizer_info: # 小程序
  384. payload['appType'] = 0
  385. else:
  386. payload['appType'] = 1
  387. funcList = []
  388. func_info = app_info['authorization_info']['func_info']
  389. for func in func_info:
  390. _id = func['funcscope_category']['id']
  391. funcList.append(_id)
  392. payload['funcList'] = funcList
  393. self.authorizer.createOrUpdateAuthRecord(payload)
  394. def do_un_auth(self, authorizer_appid):
  395. self.authorizer.deleteAuthRecord(authorizer_appid)
  396. def parse_message(self, msg, msg_signature, timestamp, nonce):
  397. """
  398. 处理 wechat server 推送消息
  399. :params msg: 加密内容
  400. :params msg_signature: 消息签名
  401. :params timestamp: 时间戳
  402. :params nonce: 随机数
  403. """
  404. content = self.crypto.decrypt_message(msg, msg_signature, timestamp, nonce)
  405. message = xmltodict.parse(to_text(content))["xml"]
  406. message_type = message["InfoType"].lower()
  407. message_class = COMPONENT_MESSAGE_TYPES.get(message_type, ComponentUnknownMessage)
  408. msg = message_class(message)
  409. if msg.type == "component_verify_ticket":
  410. self.session.set(component_verify_ticket_key(self.component_appid), msg.verify_ticket, 7 * 24 * 3600)
  411. elif msg.type in ("authorized", "updateauthorized"):
  412. self.do_auth(msg.authorization_code)
  413. elif msg.type == 'unauthorized':
  414. self.do_un_auth(msg.authorizer_appid)
  415. return msg
  416. def parse_authorizer_message(self, msg, msg_signature, timestamp, nonce):
  417. content = self.crypto.decrypt_message(msg, msg_signature, timestamp, nonce)
  418. return parse_message(content)
  419. def get_component_oauth(self, authorizer_appid):
  420. """
  421. 代公众号 OAuth 网页授权
  422. :params authorizer_appid: 授权公众号appid
  423. """
  424. return ComponentOAuth(self, authorizer_appid)
  425. class ComponentOAuth(object):
  426. """微信开放平台 代公众号 OAuth 网页授权
  427. 详情请参考
  428. https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590
  429. """
  430. API_BASE_URL = "https://api.weixin.qq.com/"
  431. OAUTH_BASE_URL = "https://open.weixin.qq.com/connect/"
  432. def __init__(self, component, app_id):
  433. """
  434. :param component: WeChatComponent
  435. :param app_id: 微信公众号 app_id
  436. """
  437. self._http = requests.Session()
  438. self.app_id = app_id
  439. self.component = component
  440. def get_authorize_url(self, redirect_uri, scope="snsapi_base", state=""):
  441. """
  442. :param redirect_uri: 重定向地址,需要urlencode,这里填写的应是服务开发方的回调地址
  443. :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base``
  444. :param state: 可选,重定向后会带上state参数,开发者可以填写任意参数值,最多128字节
  445. """
  446. redirect_uri = quote(redirect_uri, safe=b"")
  447. url_list = [
  448. self.OAUTH_BASE_URL,
  449. "oauth2/authorize?appid=",
  450. self.app_id,
  451. "&redirect_uri=",
  452. redirect_uri,
  453. "&response_type=code&scope=",
  454. scope,
  455. ]
  456. if state:
  457. url_list.extend(["&state=", state])
  458. url_list.extend(
  459. [
  460. "&component_appid=",
  461. self.component.component_appid,
  462. ]
  463. )
  464. url_list.append("#wechat_redirect")
  465. return "".join(url_list)
  466. def fetch_access_token(self, code):
  467. """获取 access_token
  468. :param code: 授权完成跳转回来后 URL 中的 code 参数
  469. :return: JSON 数据包
  470. """
  471. res = self._get(
  472. "sns/oauth2/component/access_token",
  473. params={
  474. "appid": self.app_id,
  475. "component_appid": self.component.component_appid,
  476. "component_access_token": self.component.access_token,
  477. "code": code,
  478. "grant_type": "authorization_code",
  479. },
  480. )
  481. self.access_token = res["access_token"]
  482. self.open_id = res["openid"]
  483. self.refresh_token = res["refresh_token"]
  484. self.expires_in = res["expires_in"]
  485. self.scope = res["scope"]
  486. return res
  487. def refresh_access_token(self, refresh_token):
  488. """刷新 access token
  489. :param refresh_token: OAuth2 refresh token
  490. :return: JSON 数据包
  491. """
  492. res = self._get(
  493. "sns/oauth2/component/refresh_token",
  494. params={
  495. "appid": self.app_id,
  496. "grant_type": "refresh_token",
  497. "refresh_token": refresh_token,
  498. "component_appid": self.component.component_appid,
  499. "component_access_token": self.component.access_token,
  500. },
  501. )
  502. self.access_token = res["access_token"]
  503. self.open_id = res["openid"]
  504. self.refresh_token = res["refresh_token"]
  505. self.expires_in = res["expires_in"]
  506. self.scope = res["scope"]
  507. return res
  508. def get_user_info(self, openid=None, access_token=None, lang="zh_CN"):
  509. """获取用户基本信息(需授权作用域为snsapi_userinfo)
  510. 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
  511. :param openid: 可选,微信 openid,默认获取当前授权用户信息
  512. :param access_token: 可选,access_token,默认使用当前授权用户的 access_token
  513. :param lang: 可选,语言偏好, 默认为 ``zh_CN``
  514. :return: JSON 数据包
  515. """
  516. openid = openid or self.open_id
  517. access_token = access_token or self.access_token
  518. return self._get(
  519. "sns/userinfo",
  520. params={"access_token": access_token, "openid": openid, "lang": lang},
  521. )
  522. def _request(self, method, url_or_endpoint, **kwargs):
  523. if not url_or_endpoint.startswith(("http://", "https://")):
  524. url = "{}{}".format(self.API_BASE_URL, url_or_endpoint)
  525. else:
  526. url = url_or_endpoint
  527. if isinstance(kwargs.get("data", ""), dict):
  528. body = json.dumps(kwargs["data"], ensure_ascii=False)
  529. body = body.encode("utf-8")
  530. kwargs["data"] = body
  531. res = self._http.request(method=method, url=url, **kwargs)
  532. try:
  533. res.raise_for_status()
  534. except requests.RequestException as reqe:
  535. raise WeChatOAuthException(
  536. errCode=None,
  537. errMsg=None,
  538. client=self,
  539. request=reqe.request,
  540. response=reqe.response,
  541. )
  542. return self._handle_result(res, method=method, url=url, **kwargs)
  543. def _handle_result(self, res, method=None, url=None, **kwargs):
  544. result = json.loads(res.content.decode("utf-8", "ignore"), strict=False)
  545. if "errcode" in result:
  546. result["errcode"] = int(result["errcode"])
  547. if "errcode" in result and result["errcode"] != 0:
  548. errcode = result["errcode"]
  549. errmsg = result.get("errmsg", errcode)
  550. if self.component.auto_retry and errcode in (
  551. WeChatErrorCode.INVALID_CREDENTIAL.value,
  552. WeChatErrorCode.INVALID_ACCESS_TOKEN.value,
  553. WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value,
  554. ):
  555. logger.info("Component access token expired, fetch a new one and retry request")
  556. self.component.fetch_access_token()
  557. kwargs["params"]["component_access_token"] = self.component.access_token
  558. return self._request(method=method, url_or_endpoint=url, **kwargs)
  559. elif errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
  560. # api freq out of limit
  561. raise APILimitedException(errcode, errmsg, client=self, request=res.request, response=res)
  562. else:
  563. raise WeChatComponentOAuthException(errcode, errmsg, client=self, request=res.request, response=res)
  564. return result
  565. def _get(self, url, **kwargs):
  566. return self._request(method="get", url_or_endpoint=url, **kwargs)