enterprise.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #encoding=utf-8
  2. import requests
  3. import time
  4. import urllib
  5. from .models import WxRequest, WxResponse
  6. from .models import WxArticle, WxImage, WxVoice, WxVideo, WxLink
  7. from .models import WxTextResponse, WxImageResponse, WxVoiceResponse,\
  8. WxVideoResponse, WxNewsResponse, APIError, WxEmptyResponse
  9. from .official import WxApplication as BaseApplication, WxBaseApi
  10. from .crypt import WXBizMsgCrypt
  11. __all__ = ['WxRequest', 'WxResponse', 'WxArticle', 'WxImage',
  12. 'WxVoice', 'WxVideo', 'WxLink', 'WxTextResponse',
  13. 'WxImageResponse', 'WxVoiceResponse', 'WxVideoResponse',
  14. 'WxNewsResponse', 'WxApplication',
  15. 'WxApi', 'APIError']
  16. class WxApplication(BaseApplication):
  17. UNSUPPORT_TXT = u'暂不支持此类型消息'
  18. WELCOME_TXT = u'你好!感谢您的关注!'
  19. SECRET_TOKEN = None
  20. CORP_ID = None
  21. ENCODING_AES_KEY = None
  22. def process(self, params, xml=None, token=None, corp_id=None,
  23. aes_key=None):
  24. self.token = token or self.SECRET_TOKEN
  25. self.corp_id = corp_id or self.CORP_ID
  26. self.aes_key = aes_key or self.ENCODING_AES_KEY
  27. assert self.token is not None
  28. assert self.corp_id is not None
  29. assert self.aes_key is not None
  30. timestamp = params.get('timestamp', '')
  31. nonce = params.get('nonce', '')
  32. msg_signature = params.get('msg_signature', '')
  33. echostr = params.get('echostr', '')
  34. cpt = WXBizMsgCrypt(self.token, self.aes_key, self.corp_id)
  35. err, echo = cpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
  36. if err:
  37. return 'invalid request'
  38. if not xml:
  39. return echo
  40. err, xml = cpt.DecryptMsg(xml, msg_signature, timestamp, nonce)
  41. if err:
  42. return 'decrypt message error , code %s' % err
  43. self.req = WxRequest(xml)
  44. func = self.handler_map().get(self.req.MsgType, None)
  45. if not func:
  46. return WxEmptyResponse()
  47. self.pre_process()
  48. rsp = func(self.req)
  49. self.post_process()
  50. result = rsp.as_xml().encode('UTF-8')
  51. if not result:
  52. return ''
  53. err, result = cpt.EncryptMsg(result, nonce)
  54. if err:
  55. return 'encrypt message error , code %s' % err
  56. return result
  57. def format_list(data):
  58. if data and (isinstance(data, list) or isinstance(data, tuple)):
  59. return '|'.join(data)
  60. else:
  61. return data
  62. def simplify_send_parmas(params):
  63. keys = params.keys()
  64. for key in keys:
  65. if not params[key]:
  66. del params[key]
  67. return params
  68. class WxApi(WxBaseApi):
  69. API_PREFIX = 'https://qyapi.weixin.qq.com/'
  70. def __init__(self, appid, appsecret, api_entry=None):
  71. super(WxApi, self).__init__(appid, appsecret, api_entry)
  72. self.expires_in = time.time()
  73. @property
  74. def access_token(self):
  75. if self._access_token and time.time() >= self.expires_in:
  76. self._access_token = None
  77. if not self._access_token:
  78. token, err = self.get_access_token()
  79. if not err:
  80. self._access_token = token['access_token']
  81. self.expires_in = time.time() + token['expires_in']
  82. return self._access_token
  83. else:
  84. return None
  85. return self._access_token
  86. def get_access_token(self, url=None, **kwargs):
  87. params = {'corpid': self.appid, 'corpsecret': self.appsecret}
  88. params.update(kwargs)
  89. rsp = requests.get(url or self.api_entry + 'cgi-bin/gettoken',
  90. params=params,
  91. verify=False)
  92. return self._process_response(rsp)
  93. def departments(self):
  94. return self._get('cgi-bin/department/list')
  95. def add_department(self, name, parentid='1', order=None):
  96. return self._post('cgi-bin/department/create',
  97. params={'name': name, 'parentid': parentid,
  98. 'order': order})
  99. def update_department(self, depid, name=None, parentid=None, order=None):
  100. return self._post('cgi-bin/department/update',
  101. params={'id': depid, 'name': name,
  102. 'parentid': parentid, 'order': order})
  103. def delete_department(self, depid):
  104. return self._get('cgi-bin/department/delete', params={'id': depid})
  105. def tags(self):
  106. return self._get('cgi-bin/tag/list')
  107. def add_tag(self, tagname):
  108. return self._post('cgi-bin/tag/create', {'tagname': tagname})
  109. def update_tag(self, tagid, tagname):
  110. return self._post('cgi-bin/tag/update',
  111. {'tagid': tagid, 'tagname': tagname})
  112. def delete_tag(self, tagid):
  113. return self._get('cgi-bin/tag/delete', params={'tagid': tagid})
  114. def tag_users(self, tagid):
  115. return self._get('cgi-bin/tag/get', params={'tagid': tagid})
  116. def add_tag_user(self, tagid, userlist):
  117. return self._post('cgi-bin/tag/addtagusers',
  118. {'tagid': tagid, 'userlist': userlist})
  119. def remove_tag_user(self, tagid, userlist):
  120. return self._post('cgi-bin/tag/deltagusers',
  121. {'tagid': tagid, 'userlist': userlist})
  122. def department_users(self, department_id, fetch_child=0, status=0):
  123. return self._get('cgi-bin/user/simplelist',
  124. params={'department_id': department_id,
  125. 'fetch_child': fetch_child,
  126. 'status': status})
  127. def add_user(self, userid, name, department=None, position=None,
  128. mobile=None, gender=None, tel=None, email=None,
  129. weixinid=None, extattr=None):
  130. params = {
  131. "userid": userid,
  132. "name": name,
  133. "department": department,
  134. "position": position,
  135. "mobile": mobile,
  136. "gender": gender,
  137. "tel": tel,
  138. "email": email,
  139. "weixinid": weixinid,
  140. "extattr": extattr,
  141. }
  142. return self._post('cgi-bin/user/create', params)
  143. def update_user(self, userid, name, department=None, position=None,
  144. mobile=None, gender=None, tel=None, email=None,
  145. weixinid=None, extattr=None):
  146. params = {
  147. "userid": userid,
  148. "name": name,
  149. "department": department,
  150. "position": position,
  151. "mobile": mobile,
  152. "gender": gender,
  153. "tel": tel,
  154. "email": email,
  155. "weixinid": weixinid,
  156. "extattr": extattr,
  157. }
  158. return self._post('cgi-bin/user/update', params)
  159. def delete_user(self, userid):
  160. return self._get('cgi-bin/user/delete',
  161. params={'userid': userid})
  162. def get_user(self, userid):
  163. return self._get('cgi-bin/user/get',
  164. params={'userid': userid})
  165. def upload_media(self, mtype, file_path=None, file_content=None):
  166. return super(WxApi, self).upload_media(
  167. mtype, file_path=file_path, file_content=file_content,
  168. url='cgi-bin/media/upload',
  169. suffies={'image': '.jpg', 'voice': '.mp3',
  170. 'video': '.mp4', 'file': ''})
  171. def download_media(self, media_id, to_path):
  172. return super(WxApi, self).download_media(
  173. media_id, to_path, 'cgi-bin/media/get')
  174. def send_message(self, msg_type, content, agentid, safe="0", touser=None,
  175. toparty=None, totag=None, **kwargs):
  176. func = {'text': self.send_text,
  177. 'image': self.send_image,
  178. 'voice': self.send_voice,
  179. 'video': self.send_video,
  180. 'file': self.send_file,
  181. 'news': self.send_news,
  182. 'mpnews': self.send_mpnews}.get(msg_type, None)
  183. if func:
  184. return func(content, agentid, safe=safe, touser=touser,
  185. toparty=toparty, totag=totag, **kwargs)
  186. else:
  187. return None, None
  188. def send_text(self, content, agentid, safe="0", touser=None,
  189. toparty=None, totag=None):
  190. return self._post(
  191. 'cgi-bin/message/send',
  192. simplify_send_parmas({'touser': format_list(touser),
  193. 'toparty': format_list(toparty),
  194. 'totag': format_list(totag),
  195. 'msgtype': 'text',
  196. 'agentid': agentid,
  197. 'safe': safe,
  198. 'text': {'content': content}
  199. }))
  200. def send_simple_media(self, mtype, media_id, agentid, safe="0",
  201. touser=None, toparty=None, totag=None,
  202. media_url=None):
  203. if media_id and media_id.startswith('http'):
  204. media_url = media_id
  205. media_id = None
  206. mid = self._get_media_id(
  207. {'media_id': media_id, 'media_url': media_url},
  208. 'media', mtype)
  209. return self._post(
  210. 'cgi-bin/message/send',
  211. simplify_send_parmas({'touser': format_list(touser),
  212. 'toparty': format_list(toparty),
  213. 'totag': format_list(totag),
  214. 'msgtype': mtype,
  215. 'agentid': agentid,
  216. 'safe': safe,
  217. mtype: {'media_id': mid}
  218. }))
  219. def send_image(self, media_id, agentid, safe="0", touser=None,
  220. toparty=None, totag=None, media_url=None):
  221. return self.send_simple_media('image', media_id, agentid, safe,
  222. touser, toparty, totag, media_url)
  223. def send_voice(self, media_id, agentid, safe="0", touser=None,
  224. toparty=None, totag=None, media_url=None):
  225. return self.send_simple_media('voice', media_id, agentid, safe,
  226. touser, toparty, totag, media_url)
  227. def send_file(self, media_id, agentid, safe="0", touser=None,
  228. toparty=None, totag=None, media_url=None):
  229. return self.send_simple_media('file', media_id, agentid, safe,
  230. touser, toparty, totag, media_url)
  231. def send_video(self, video, agentid, safe="0", touser=None,
  232. toparty=None, totag=None, media_url=None):
  233. video['media_id'] = self._get_media_id(video, 'media', 'video')
  234. if 'media_url' in video:
  235. del video['media_url']
  236. return self._post(
  237. 'cgi-bin/message/send',
  238. simplify_send_parmas({'touser': format_list(touser),
  239. 'toparty': format_list(toparty),
  240. 'totag': format_list(totag),
  241. 'msgtype': 'video',
  242. 'agentid': agentid,
  243. 'safe': safe,
  244. 'video': video}))
  245. def send_news(self, news, agentid, safe="0", touser=None,
  246. toparty=None, totag=None, media_url=None):
  247. if isinstance(news, dict):
  248. news = [news]
  249. return self._post(
  250. 'cgi-bin/message/send',
  251. simplify_send_parmas({'touser': format_list(touser),
  252. 'toparty': format_list(toparty),
  253. 'totag': format_list(totag),
  254. 'msgtype': 'news',
  255. 'agentid': agentid,
  256. 'safe': safe,
  257. 'news': {'articles': news}}))
  258. def send_mpnews(self, mpnews, agentid, safe="0", touser=None,
  259. toparty=None, totag=None, media_url=None):
  260. if isinstance(mpnews, dict):
  261. news = [mpnews]
  262. return self._post(
  263. 'cgi-bin/message/send',
  264. simplify_send_parmas({'touser': format_list(touser),
  265. 'toparty': format_list(toparty),
  266. 'totag': format_list(totag),
  267. 'msgtype': 'mpnews',
  268. 'agentid': agentid,
  269. 'safe': safe,
  270. 'mpnews': {'articles': news}}))
  271. def create_menu(self, menus, agentid):
  272. return self._post('cgi-bin/menu/create?agentid=%s' % agentid,
  273. repr(menus), ctype='text')
  274. def get_menu(self, agentid):
  275. return self._get('cgi-bin/menu/get', {'agentid': agentid})
  276. def delete_menu(self, agentid):
  277. return self._get('cgi-bin/menu/delete', {'agentid': agentid})
  278. # OAuth2
  279. def authorize_url(self, appid, redirect_uri, response_type='code',
  280. scope='snsapi_base', state=None):
  281. # 变态的微信实现,参数的顺序也有讲究。。艹!这个实现太恶心,太恶心!
  282. url = 'https://open.weixin.qq.com/connect/oauth2/authorize?'
  283. rd_uri = urllib.urlencode({'redirect_uri': redirect_uri})
  284. url += 'appid=%s&' % appid
  285. url += rd_uri
  286. url += '&response_type=' + response_type
  287. url += '&scope=' + scope
  288. if state:
  289. url += '&state=' + state
  290. return url + '#wechat_redirect'
  291. def get_user_info(self, agentid, code):
  292. return self._get('cgi-bin/user/getuserinfo',
  293. {'agentid': agentid, 'code': code})