AlexaController.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # @Author : Rocky
  2. # @File : AlexaController.py
  3. # @Time : 2023/12/25 10:46
  4. import time
  5. import requests
  6. from django.views import View
  7. from Model.models import AlexaOauth
  8. from Object.ResponseObject import ResponseObject
  9. from Object.TokenObject import TokenObject
  10. from Ansjer.config import CONFIG_INFO, CONFIG_EUR
  11. class AppToAppView(View):
  12. def get(self, request, *args, **kwargs):
  13. request.encoding = 'utf-8'
  14. operation = kwargs.get('operation')
  15. return self.validation(request.GET, operation, request)
  16. def post(self, request, *args, **kwargs):
  17. request.encoding = 'utf-8'
  18. operation = kwargs.get('operation')
  19. return self.validation(request.POST, operation, request)
  20. def validation(self, request_dict, operation, request):
  21. response = ResponseObject()
  22. token = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
  23. if token.code != 0:
  24. return response.json(token.code)
  25. user_id = token.userID
  26. if operation == 'getAlexaAppURLAndLWAFallbackURL': # 获取Alexa App和LWA fallback URL
  27. return self.get_alexa_app_url_and_lwa_fallback_url(response)
  28. elif operation == 'accountLinkWithAmazonAuthorizationCode': # 通过亚马逊授权码连接账号
  29. return self.account_link_with_amazon_authorization_code(user_id, request_dict, response)
  30. elif operation == 'getAccountLinkingAndSkillStatus': # 获取账号连接和skill状态
  31. return self.get_account_linking_and_skill_status(user_id, response)
  32. elif operation == 'disableSkillAndUnlinkAccount': # 取消连接skill和账号
  33. return self.disable_skill_and_unlink_account(user_id, response)
  34. elif operation == 'getSkillPageURL': # 获取skill页面URL(取消链接)
  35. return self.get_skill_page_url(response)
  36. else:
  37. return response.json(414)
  38. @staticmethod
  39. def get_alexa_app_url_and_lwa_fallback_url(response):
  40. client_id = 'amzn1.application-oa2-client.98a01914518743e481d51115144dafb0' # Alexa Client Id
  41. skill_stage = 'development' # 开发中: development, 已上线: live
  42. redirect_uri = 'https://smart.loocam2.com'
  43. alexa_app_url = 'https://alexa.amazon.com/spa/skill-account-linking-consent?' \
  44. 'fragment=skill-account-linking-consent&client_id={}&' \
  45. 'scope=alexa::skills:account_linking&skill_stage={}&response_type=code&' \
  46. 'redirect_uri={}'.format(client_id, skill_stage, redirect_uri)
  47. lwa_fallback_url = 'https://www.amazon.com/ap/oa?' \
  48. 'client_id={}&scope=alexa::skills:account_linking&response_type=code&redirect_uri={}&'.\
  49. format(client_id, redirect_uri)
  50. res = {
  51. 'alexa_app_url': alexa_app_url,
  52. 'lwa_fallback_url': lwa_fallback_url
  53. }
  54. return response.json(0, res)
  55. @staticmethod
  56. def account_link_with_amazon_authorization_code(user_id, request_dict, response):
  57. amazon_authorization_code = request_dict.get('amazon_authorization_code', None)
  58. if not amazon_authorization_code:
  59. return response.json(444)
  60. now_time = int(time.time())
  61. # 获取亚马逊访问令牌
  62. # https://developer.amazon.com/zh/docs/login-with-amazon/authorization-code-grant.html#access-token-request
  63. amazon_base_uri = 'https://api.amazon.com'
  64. url = amazon_base_uri + '/auth/o2/token'
  65. redirect_uri = 'https://smart.loocam2.com'
  66. data = {
  67. 'grant_type': 'authorization_code',
  68. 'code': amazon_authorization_code,
  69. 'client_id': 'amzn1.application-oa2-client.98a01914518743e481d51115144dafb0',
  70. 'client_secret': '43353cac67670aefd64a5f95309754ddd6bcfe8a087cc3cad1348b626f64b132',
  71. 'redirect_uri': redirect_uri
  72. }
  73. try:
  74. r = requests.post(url=url, data=data, timeout=10)
  75. assert r.status_code == 200
  76. res_data = eval(r.content)
  77. assert res_data.get('access_token')
  78. assert res_data.get('refresh_token')
  79. amazon_access_token = res_data['access_token']
  80. amazon_refresh_token = res_data['refresh_token']
  81. # 保存令牌数据
  82. alexa_oauth_qs = AlexaOauth.objects.filter(user_id=user_id)
  83. if alexa_oauth_qs.exists():
  84. alexa_oauth_qs.update(amazon_access_token=amazon_access_token,
  85. amazon_refresh_token=amazon_refresh_token,
  86. update_time=now_time)
  87. else:
  88. AlexaOauth.objects.create(user_id=user_id, amazon_access_token=amazon_access_token,
  89. amazon_refresh_token=amazon_refresh_token, create_time=now_time,
  90. update_time=now_time)
  91. # 获取用户授权码
  92. url = redirect_uri + '/appToApp/oa2/getAuthCode'
  93. region_code = 'EU'
  94. if CONFIG_INFO != CONFIG_EUR:
  95. region_code = 'US'
  96. params = {
  97. 'user_id': user_id,
  98. 'region_code': region_code
  99. }
  100. r = requests.get(url=url, params=params, timeout=10)
  101. assert r.status_code == 200
  102. res = eval(r.content)
  103. user_authorization_code = res['res']['user_authorization_code']
  104. data = {
  105. "stage": "development",
  106. "accountLinkRequest": {
  107. "redirectUri": redirect_uri,
  108. "authCode": user_authorization_code,
  109. "type": "AUTH_CODE"
  110. }
  111. }
  112. # 请求连接skill
  113. # https://developer.amazon.com/en-US/docs/alexa/smapi/skill-enablement.html
  114. headers = {
  115. 'Content-Type': 'application/json',
  116. 'Authorization': 'Bearer {}'.format(amazon_access_token)
  117. }
  118. alexa_api_endpoint_list = ['api.amazonalexa.com', 'api.eu.amazonalexa.com', 'api.fe.amazonalexa.com']
  119. skill_id = 'amzn1.ask.skill.ff5a5074-7ec7-442b-979b-cb57095f7a94'
  120. for alexa_api_endpoint in alexa_api_endpoint_list:
  121. url = 'https://{}/v1/users/~current/skills/{}/enablement'.format(alexa_api_endpoint, skill_id)
  122. r = requests.post(headers=headers, url=url, json=data, timeout=30)
  123. if r.status_code == 201:
  124. AlexaOauth.objects.filter(user_id=user_id).\
  125. update(alexa_api_endpoint=alexa_api_endpoint, link_status=1)
  126. res_data = eval(r.content)
  127. return response.json(0, res_data)
  128. return response.json(0)
  129. except Exception as e:
  130. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  131. @classmethod
  132. def get_account_linking_and_skill_status(cls, user_id, response):
  133. # 未连接状态响应数据
  134. res_data = {
  135. 'accountLink': {
  136. 'status': 'NOT_LINKED'
  137. },
  138. 'status': 'DISABLED'
  139. }
  140. try:
  141. alexa_oauth_qs = AlexaOauth.objects.filter(user_id=user_id).values('link_status')
  142. if alexa_oauth_qs.exists():
  143. link_status = alexa_oauth_qs[0]['link_status']
  144. # 连接状态为1,通过api获取状态
  145. if link_status == 1:
  146. request_method = 'get'
  147. r = cls.refresh_access_token(user_id, request_method)
  148. if r is None:
  149. return response.json(173)
  150. res_data = eval(r.content)
  151. return response.json(0, res_data)
  152. except Exception as e:
  153. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  154. @classmethod
  155. def disable_skill_and_unlink_account(cls, user_id, response):
  156. request_method = 'delete'
  157. try:
  158. r = cls.refresh_access_token(user_id, request_method)
  159. if r is None:
  160. return response.json(173)
  161. # 2xx响应状态码为成功
  162. assert str(r.status_code)[:1] == '2'
  163. AlexaOauth.objects.filter(user_id=user_id).update(link_status=0)
  164. return response.json(0)
  165. except Exception as e:
  166. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  167. @staticmethod
  168. def get_skill_page_url(response):
  169. skill_asin = 'B0C94Q7H1L'
  170. skill_page_url = 'https://alexa.amazon.com/spa/index.html#skills/dp/{}'.format(skill_asin)
  171. lwa_page_url = 'https://www.amazon.com/dp/{}'.format(skill_asin)
  172. res = {
  173. 'skill_page_url': skill_page_url,
  174. 'lwa_page_url': lwa_page_url
  175. }
  176. return response.json(0, res)
  177. @staticmethod
  178. def refresh_access_token(user_id, request_method):
  179. if request_method not in ['get', 'delete']:
  180. return None
  181. alexa_oauth_qs = AlexaOauth.objects.filter(user_id=user_id).values('alexa_api_endpoint', 'amazon_refresh_token')
  182. if not alexa_oauth_qs:
  183. return None
  184. now_time = int(time.time())
  185. # 使用刷新令牌获取新的访问令牌
  186. # https://developer.amazon.com/zh/docs/login-with-amazon/authorization-code-grant.html#using-refresh-tokens
  187. alexa_api_endpoint = alexa_oauth_qs[0]['alexa_api_endpoint']
  188. amazon_refresh_token = alexa_oauth_qs[0]['amazon_refresh_token']
  189. amazon_base_uri = 'https://api.amazon.com'
  190. url = amazon_base_uri + '/auth/o2/token'
  191. data = {
  192. 'grant_type': 'refresh_token',
  193. 'refresh_token': amazon_refresh_token,
  194. 'client_id': 'amzn1.application-oa2-client.98a01914518743e481d51115144dafb0',
  195. 'client_secret': '43353cac67670aefd64a5f95309754ddd6bcfe8a087cc3cad1348b626f64b132'
  196. }
  197. r = requests.post(url=url, data=data, timeout=10)
  198. assert r.status_code == 200
  199. res_data = eval(r.content)
  200. assert res_data.get('access_token')
  201. assert res_data.get('refresh_token')
  202. new_access_token = res_data['access_token']
  203. new_refresh_token = res_data['refresh_token']
  204. alexa_oauth_qs.update(amazon_access_token=new_access_token, amazon_refresh_token=new_refresh_token,
  205. update_time=now_time)
  206. headers = {
  207. 'Content-Type': 'application/json',
  208. 'Authorization': 'Bearer {}'.format(new_access_token)
  209. }
  210. skill_id = 'amzn1.ask.skill.ff5a5074-7ec7-442b-979b-cb57095f7a94'
  211. url = 'https://{}/v1/users/~current/skills/{}/enablement'.format(alexa_api_endpoint, skill_id)
  212. if request_method == 'get':
  213. r = requests.get(headers=headers, url=url, timeout=30)
  214. elif request_method == 'delete':
  215. r = requests.delete(headers=headers, url=url, timeout=30)
  216. return r