From 2a1082505a88bf242be1fb7019ebe025e0b1e1e2 Mon Sep 17 00:00:00 2001 From: csrftoken Date: Mon, 29 Jul 2024 16:49:19 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=85=B6=E5=AE=83=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/alipay/main.py | 21 +++-- examples/tanmarket/field_map.py | 4 + examples/taobao/__init__.py | 3 + examples/taobao/main.py | 28 ++++++ examples/xhs/main.py | 68 -------------- examples/{xhs => xiaohongshu}/__init__.py | 0 examples/xiaohongshu/main.py | 94 +++++++++++++++++++ .../mocks/batchDecrypt.json | 0 openapi/providers/taobao.py | 51 ++++++++++ openapi/providers/xhs.py | 2 +- 10 files changed, 193 insertions(+), 78 deletions(-) create mode 100644 examples/taobao/__init__.py create mode 100644 examples/taobao/main.py delete mode 100644 examples/xhs/main.py rename examples/{xhs => xiaohongshu}/__init__.py (100%) create mode 100644 examples/xiaohongshu/main.py rename examples/{xhs => xiaohongshu}/mocks/batchDecrypt.json (100%) create mode 100644 openapi/providers/taobao.py diff --git a/examples/alipay/main.py b/examples/alipay/main.py index c8de971..09dd1f0 100644 --- a/examples/alipay/main.py +++ b/examples/alipay/main.py @@ -1,11 +1,13 @@ +import json + import httpx from openapi.providers.alipay import Client from examples.config import config -settings = config['alipay-prod2'] -client = Client(**settings, is_sandbox=False) +settings = config['alipay-test'] +client = Client(**settings, is_sandbox=True) # client.add_webhook(config['openapi_webhook']) @@ -18,23 +20,24 @@ # data={'bill_type': 'trade', 'bill_date': '2023-05'} )) request_url = f'{client.API_BASE_URL}?{params}' - print(request_url) - response = httpx.get(request_url) - print(response.json()) + # print(request_url) + # response = httpx.get(request_url) + # print(response.json()) # pc-pay pc_pay_params = client.build_query_params(client.build_params( 'alipay.trade.page.pay', { 'subject': 'popmart-molly', - 'out_trade_no': 'pc1234567', - 'total_amount': '0.01', - 'product_code': 'FAST_INSTANT_TRADE_PAY' + 'out_trade_no': 'pc12345678', + 'total_amount': '100', + 'product_code': 'FAST_INSTANT_TRADE_PAY', + 'extend_params': json.dumps({'hb_fq_seller_percent': '100'}) }, notify_url='http://47.94.172.250:9527/api/v1/pay/alipay/', return_url='http://47.94.172.250:9527/api/v1/pay/alipay/' )) pc_pay_url = f'{client.API_BASE_URL}?{pc_pay_params}' - # print(pc_pay_url) + print(pc_pay_url) # mobile-pay mobile_pay_params = client.build_query_params(client.build_params( diff --git a/examples/tanmarket/field_map.py b/examples/tanmarket/field_map.py index 4c8c201..b1c43b9 100644 --- a/examples/tanmarket/field_map.py +++ b/examples/tanmarket/field_map.py @@ -83,6 +83,10 @@ { 'fieldId': '138623', 'alias': '大航海营期' + }, + { + 'fieldId': '144541', + 'alias': '3天Python营' } ] diff --git a/examples/taobao/__init__.py b/examples/taobao/__init__.py new file mode 100644 index 0000000..60bcc63 --- /dev/null +++ b/examples/taobao/__init__.py @@ -0,0 +1,3 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# Date: 2024/1/8 diff --git a/examples/taobao/main.py b/examples/taobao/main.py new file mode 100644 index 0000000..8d31189 --- /dev/null +++ b/examples/taobao/main.py @@ -0,0 +1,28 @@ +from openapi.providers.taobao import Client, calculate_signature + + +d = { + 'app_key': '33713810', + 'method': 'taobao.trade.fullinfo.get', + 'v': '2.0', + 'timestamp': '2024-01-09 00:00:03', + 'partner_id': 'top-apitools', + 'session': '6100a040fa611a381b3cd37eca8a1c31df1ff56c6970e642212108025244', + 'format': 'json', + 'sign_method': 'md5', + 'fields': 'tid,type,status,payment,orders,promotion_details', + 'tid': '3725362658812595205' +} +print(calculate_signature(d, 'd78cffb2b73b4d090a9b06e0f289c472')) + +client = Client( + app_id='33713810', secret='d78cffb2b73b4d090a9b06e0f289c472' +) +r = client.request( + 'get', 'taobao.trade.fullinfo.get', + data={ + 'fields': 'tid,type,status,payment,orders,promotion_details', + 'tid': '3725362658812595205' + } +) +print(r) diff --git a/examples/xhs/main.py b/examples/xhs/main.py deleted file mode 100644 index aff8ce7..0000000 --- a/examples/xhs/main.py +++ /dev/null @@ -1,68 +0,0 @@ -from datetime import datetime -from openapi.providers.xhs import Client - -from examples.config import config - -# print(config) -client = Client( - app_id=config['xhs']['app_id'], secret=config['xhs']['secret'], - user_id=config['xhs']['user_id'], seller_id=config['xhs']['seller_id'] -) -client.add_webhook(config['openapi_webhook']) -order_id = config['xhs']['test']['order_id'] - -info = config['xhs']['test']['info'] -info['expires_at'] = datetime.fromtimestamp(info['expires_at'] / 1000) - -# r = client.request( -# 'post', '/ark/open_api/v3/common_controller', -# action='oauth.getAccessToken', -# data={ -# 'code': config['xhs']['code'] -# } -# ) -# print(r) - -r = client.request( - 'post', '/ark/open_api/v3/common_controller', - action='order.getOrderDetail', - data={ - 'orderId': 'P718870545250225361' - } -) -address_id = r.data['openAddressId'] -print(address_id) - -# r = client.request( -# 'post', '/ark/open_api/v3/common_controller', -# action='order.getOrderReceiverInfo', -# data={ -# 'accessToken': config['xhs']['token'], -# 'receiverQueries': { -# 'orderId': 'P718870545250225361', -# 'openAddressId': address_id -# } -# } -# ) -# print(r) -# client.make_token(**info) - -r = client.request( - 'post', '/ark/open_api/v3/common_controller', - action='data.batchDecrypt', - data={ - 'baseInfos': [ - { - 'dataTag': order_id, - 'encryptedData': '#O/TUHCniXiCEbZKi06W0NE3aCHmiw6hlDxi1wabfz2U=#O/TUHCniXiCEbZKi06W0NAP/QQxFlmN4IBXB3iu5Csc1kGKQhS693psz9eci0XbEFlowN4IyD9VUDhL2eX2k5h2kTY53vwktNnxKVy83nWw=#2##' - }, - { - 'dataTag': order_id, - 'encryptedData': '#O/TUHCniXiCEbZKi06W0NHqMLy7peAQiu8VRjWCmJVA=#O/TUHCniXiCEbZKi06W0NAP/QQxFlmN4IBXB3iu5Csc1kGKQhS693psz9eci0XbEV2DSoCHAYct0sQIvVGxQz6zf/OswYhtN4W9+jxWAnsc=#3##' - }, - ], - 'actionType': 2, 'appUserId': '' - } -) -print(r) - diff --git a/examples/xhs/__init__.py b/examples/xiaohongshu/__init__.py similarity index 100% rename from examples/xhs/__init__.py rename to examples/xiaohongshu/__init__.py diff --git a/examples/xiaohongshu/main.py b/examples/xiaohongshu/main.py new file mode 100644 index 0000000..6f727fb --- /dev/null +++ b/examples/xiaohongshu/main.py @@ -0,0 +1,94 @@ +from datetime import datetime + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.hashes import MD5, Hash +from openapi.providers.xhs import Client + +from examples.config import config + +# print(config) +client = Client( + app_id=config['xhs']['app_id'], secret=config['xhs']['secret'], + user_id=config['xhs']['user_id'], seller_id=config['xhs']['seller_id'] +) +client.add_webhook(config['openapi_webhook']) +order_id = config['xhs']['test']['order_id'] + +info = config['xhs']['test']['info'] +info['expires_at'] = datetime.fromtimestamp(info['expires_at'] / 1000) + +# r = client.request( +# 'post', '/ark/open_api/v3/common_controller', +# action='oauth.getAccessToken', +# data={ +# 'code': config['xhs']['code'] +# } +# ) +# print(r) + +# r = client.request( +# 'post', '/ark/open_api/v3/common_controller', +# action='order.getOrderDetail', +# data={ +# 'orderId': order_id +# } +# ) +# address_id = r.data['openAddressId'] +# print(r) + +# r = client.request( +# 'post', '/ark/open_api/v3/common_controller', +# action='order.getOrderReceiverInfo', +# data={ +# 'receiverQueries': { +# 'orderId': order_id, +# 'openAddressId': address_id +# } +# } +# ) +# print(r) +# client.make_token(**info) + +# r = client.request( +# 'post', '/ark/open_api/v3/common_controller', +# action='data.batchDecrypt', +# data={ +# 'baseInfos': [ +# { +# 'dataTag': order_id, +# 'encryptedData': r.data['receiverInfos'][0]['receiverName'] +# }, +# { +# 'dataTag': order_id, +# 'encryptedData': r.data['receiverInfos'][0]['receiverPhone'] +# }, +# ], +# 'actionType': 2, 'appUserId': '' +# } +# ) +# print(r) + + +r = client.request( + 'post', '/ark/open_api/v3/common_controller', + action='common.getCategories', data={'categoryId': '5a310e243f7d1712823ba480'} +) +print(r) + + +def calculate_signature(params, api_key): + data = f'{params["path"]}?app-key={params["app-key"]}×tamp={params["timestamp"]}' + h = Hash(algorithm=MD5(), backend=default_backend()) + h.update(f'{data}{api_key}'.encode('utf-8')) + return h.finalize().hex() + + +sign = calculate_signature( + params={ + 'path': '', + 'app-key': client.app_id, + 'timestamp': '17033916261', + }, + api_key=client.secret +) +print(sign) diff --git a/examples/xhs/mocks/batchDecrypt.json b/examples/xiaohongshu/mocks/batchDecrypt.json similarity index 100% rename from examples/xhs/mocks/batchDecrypt.json rename to examples/xiaohongshu/mocks/batchDecrypt.json diff --git a/openapi/providers/taobao.py b/openapi/providers/taobao.py new file mode 100644 index 0000000..eb48e2f --- /dev/null +++ b/openapi/providers/taobao.py @@ -0,0 +1,51 @@ +from datetime import datetime +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.hashes import MD5, Hash + +from openapi.providers.base import BaseClient, BaseResult + + +def calculate_signature(params, secret): + print(params) + keys = list(params.keys()) + keys.sort() + parameters = f"{secret}{str().join(f'{key}{params[key]}' for key in keys)}{secret}" + h = Hash(algorithm=MD5(), backend=default_backend()) + h.update(parameters.encode('utf-8')) + return h.finalize().hex().upper() + + +class Result(BaseResult): + pass + + +class Client(BaseClient): + NAME = '淘宝开放平台' + VERSION = '2.0' + API_BASE_URL = 'https://eco.taobao.com/router/rest' + + def __init__(self, app_id, secret): + super().__init__() + self.app_id = app_id + self.secret = secret + # hmac,md5,hmac-sha256 + self.sign_method = 'md5' + + def request(self, method, action, params=None, data=None, token_request=False): + public_params = { + 'app_key': self.app_id, + 'method': action, + 'v': self.VERSION, + 'timestamp': datetime.now().strftime('%Y-%m-%d %X'), + 'partner_id': 'top-apitools', + 'session': '6100a040fa611a381b3cd37eca8a1c31df1ff56c6970e642212108025244', + 'format': 'json', + 'sign_method': self.sign_method, + } + public_params['sign'] = calculate_signature( + {**public_params, **data}, + self.secret + ) + response = self._request(method, self.API_BASE_URL, params={**public_params, **data}, json=data) + print(response.json()) + return Result(**response.json()) diff --git a/openapi/providers/xhs.py b/openapi/providers/xhs.py index 0e36d28..e6fab56 100644 --- a/openapi/providers/xhs.py +++ b/openapi/providers/xhs.py @@ -1,5 +1,5 @@ -import datetime import time +import datetime from typing import Optional from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.hashes import MD5, Hash From 06e01dff2472d1ef0607035ba9be40dce4dac281 Mon Sep 17 00:00:00 2001 From: csrftoken Date: Mon, 29 Jul 2024 16:56:33 +0800 Subject: [PATCH 2/5] feat: update --- examples/taobao/main.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/taobao/main.py b/examples/taobao/main.py index 8d31189..9935ea0 100644 --- a/examples/taobao/main.py +++ b/examples/taobao/main.py @@ -1,8 +1,11 @@ from openapi.providers.taobao import Client, calculate_signature +from examples.config import config +app_key = config['taobao']['app_key'] +secret = config['taobao']['secret'] d = { - 'app_key': '33713810', + 'app_key': app_key, 'method': 'taobao.trade.fullinfo.get', 'v': '2.0', 'timestamp': '2024-01-09 00:00:03', @@ -13,11 +16,9 @@ 'fields': 'tid,type,status,payment,orders,promotion_details', 'tid': '3725362658812595205' } -print(calculate_signature(d, 'd78cffb2b73b4d090a9b06e0f289c472')) +print(calculate_signature(d, secret)) -client = Client( - app_id='33713810', secret='d78cffb2b73b4d090a9b06e0f289c472' -) +client = Client(app_id=app_key, secret=secret) r = client.request( 'get', 'taobao.trade.fullinfo.get', data={ From b0d43a4d2b65e772d9ae3c7a3770ce11c867b0c5 Mon Sep 17 00:00:00 2001 From: csrftoken Date: Mon, 5 Aug 2024 09:52:30 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E5=BF=AB=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi/providers/kwai.py | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 openapi/providers/kwai.py diff --git a/openapi/providers/kwai.py b/openapi/providers/kwai.py new file mode 100644 index 0000000..6aa9003 --- /dev/null +++ b/openapi/providers/kwai.py @@ -0,0 +1,56 @@ +import time +import datetime +from typing import Optional +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.hashes import MD5, Hash + +from openapi.enums import IntegerChoices +from openapi.providers.base import BaseClient, BaseResult, Token + + +def calculate_signature(params, api_key): + data = f'appId={params["appId"]}×tamp={params["timestamp"]}&version={params["version"]}' + h = Hash(algorithm=MD5(), backend=default_backend()) + h.update(f'{params["method"]}?{data}{api_key}'.encode('utf-8')) + return h.finalize().hex() + + +class Code(IntegerChoices): + FAIL = -1, '失败' + SUCCESS = 0, '成功' + INVALID_WHITE_LIST = 2051, 'ip 未在白名单' + + +class Result(BaseResult): + success: bool + error_code: Optional[int] + error_msg: Optional[str] + + +class Client(BaseClient): + NAME = '快手' + API_VERSION = 1 + API_BASE_URL = 'https://openapi.kwaixiaodian.com' + + def __init__(self, app_id, secret): + super().__init__() + self.app_id = app_id + self.secret = secret + self.codes = Code + + def request(self, method, endpoint, action=None, params=None, data=None, token_request=False): + if data is not None: + public_params = { + 'appkey': self.app_id, + 'timestamp': str(int(time.time() * 1000)), + 'access_token': '', + 'version': self.API_VERSION, + 'method': action, + 'signMethod': 'HMAC_SHA256', + } + public_params['sign'] = calculate_signature(public_params, self.secret) + public_params['param'] = None + + request_url = f'{self.API_BASE_URL}{endpoint}' + response = self._request(method, request_url, params=params, json=data) + return Result(**response.json()) From ca1037ad4489fa8a8df33af087f77ce661d05884 Mon Sep 17 00:00:00 2001 From: csrftoken Date: Tue, 13 Aug 2024 23:49:26 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(kuaishou):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=BF=AB=E6=89=8B=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=20api=20?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/kwai/__init__.py | 0 examples/kwai/main.py | 10 ++++++++++ openapi/providers/kwai.py | 27 +++++++++++++++++---------- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 examples/kwai/__init__.py create mode 100644 examples/kwai/main.py diff --git a/examples/kwai/__init__.py b/examples/kwai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/kwai/main.py b/examples/kwai/main.py new file mode 100644 index 0000000..c2a45d5 --- /dev/null +++ b/examples/kwai/main.py @@ -0,0 +1,10 @@ +from openapi.providers.kwai import Client + +from examples.config import config + +client = Client('', '', config['kuaishou']['message_key']) + + +if __name__ == '__main__': + # 快手消息解密 + print(client.decrypt('2FXtJpaJ1ftNz39omFhoWQ==')) diff --git a/openapi/providers/kwai.py b/openapi/providers/kwai.py index 6aa9003..e7d4db0 100644 --- a/openapi/providers/kwai.py +++ b/openapi/providers/kwai.py @@ -1,6 +1,7 @@ import time -import datetime +import base64 from typing import Optional +from Crypto.Cipher import AES from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.hashes import MD5, Hash @@ -32,25 +33,31 @@ class Client(BaseClient): API_VERSION = 1 API_BASE_URL = 'https://openapi.kwaixiaodian.com' - def __init__(self, app_id, secret): + def __init__(self, app_id, secret, message_key): super().__init__() self.app_id = app_id self.secret = secret + self.message_key = message_key self.codes = Code def request(self, method, endpoint, action=None, params=None, data=None, token_request=False): + public_params = {} if data is not None: - public_params = { - 'appkey': self.app_id, - 'timestamp': str(int(time.time() * 1000)), - 'access_token': '', - 'version': self.API_VERSION, - 'method': action, - 'signMethod': 'HMAC_SHA256', - } + public_params.update(**{ + 'appkey': self.app_id, 'timestamp': str(int(time.time() * 1000)), 'access_token': '', + 'version': self.API_VERSION, 'method': action, 'signMethod': 'HMAC_SHA256' + }) public_params['sign'] = calculate_signature(public_params, self.secret) public_params['param'] = None request_url = f'{self.API_BASE_URL}{endpoint}' response = self._request(method, request_url, params=params, json=data) return Result(**response.json()) + + def decrypt(self, message): + crypt = base64.b64decode(message) + key = base64.b64decode(self.message_key) + block = AES.new(key, AES.MODE_CBC, iv=bytes(AES.block_size)) + decrypt_data = block.decrypt(crypt) + length = len(decrypt_data) + return decrypt_data[:length - int(decrypt_data[length - 1])].decode() From 8a33a97d0481ca074338151fb1ea5dabb1eb1cd7 Mon Sep 17 00:00:00 2001 From: csrftoken Date: Thu, 15 Aug 2024 00:10:21 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(kuaishou):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=BF=AB=E6=89=8B=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=20api=20?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/kwai/main.py | 43 +++++- openapi/providers/kwai.py | 55 ++++--- tests/__init__.py | 0 tests/resources/kuaishou/order.detail.json | 167 +++++++++++++++++++++ 4 files changed, 241 insertions(+), 24 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/resources/kuaishou/order.detail.json diff --git a/examples/kwai/main.py b/examples/kwai/main.py index c2a45d5..33122f3 100644 --- a/examples/kwai/main.py +++ b/examples/kwai/main.py @@ -1,10 +1,49 @@ -from openapi.providers.kwai import Client +from openapi.providers.kwai import Client, Token from examples.config import config -client = Client('', '', config['kuaishou']['message_key']) +client = Client( + config['kuaishou']['app_key'], + config['kuaishou']['secret'], + config['kuaishou']['sign_secret'], + config['kuaishou']['message_key'] +) if __name__ == '__main__': # 快手消息解密 print(client.decrypt('2FXtJpaJ1ftNz39omFhoWQ==')) + print(client.decrypt('jbaDDoPZu/61uO/SMXT2s/y5Jq1+ISvE1QSo1kGE/p8jxJwkTNrIYDjvmtAQlwYUaDiYBSQR+hCwJWx299xZyubTYLDgmzL45dIchVAOyqiSnqKJLwnaXU1+W0lsRfKnvuBQvQhd6ftubauZU0RfvvgpEli/qZug0WPZMahHqq7pym/16x4GbzSMy4NzSevZCuVvYNIHCYeGYj+DkR18hiyUj08nHm/9y+70lGjcwBp7KjQ92XjUX0hqteTTgr7rvSJC00xA9gV2XafJAKo7cvTI0NT56ivyh2sM/Jt+XA2zlB8GpdL2zoTnSZKBXFaM+LhsE2kNHYvlZnjbonq3JvuXSIBPXnt9DdEJgEXXg5pKju3RKLGrTDSmB1rVy7r7YnyobQ/r0w8N4sIUs3FEVlB4fAds9slOsr5kY3YwQ2DRxNmiubbpjfjipW3H2jtMmRPToKP/2BChl+P9KIZ41X1Xykldz/2vFVo87C0S4FkzXsCfQrMm7t4a4nx8ktmHIME9BW2ashkdM/AAlxK9Rwe0+F7GFJ7xHICQUBI8hVM=')) + + # 授权获取 Token + # client.request( + # 'get', '/oauth2/access_token', + # params={ + # 'app_id': client.app_id, 'grant_type': 'code', + # 'code': '', + # 'app_secret': client.secret + # } + # ) + + client._token = Token(access_token=config['kuaishou']['test']['access_token']) + # 获取订单详情 + result = client.request( + 'get', '/open/order/detail', action='open.order.detail', + params={ + 'oid': 2421300093168518 + } + ) + print(result) + # 解密 + result = client.request( + 'post', '/open/order/desensitise/batch', action='open.order.desensitise.batch', + data={ + 'batchDesensitiseList': [ + { + 'encryptedData': result.data['orderAddress']['encryptedMobile'], + 'bizId': 2421300093168518 + } + ] + } + ) + print(result) diff --git a/openapi/providers/kwai.py b/openapi/providers/kwai.py index e7d4db0..34aa0d7 100644 --- a/openapi/providers/kwai.py +++ b/openapi/providers/kwai.py @@ -1,30 +1,36 @@ +import json import time +import hmac +import hashlib import base64 from typing import Optional from Crypto.Cipher import AES -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.hashes import MD5, Hash from openapi.enums import IntegerChoices from openapi.providers.base import BaseClient, BaseResult, Token -def calculate_signature(params, api_key): - data = f'appId={params["appId"]}×tamp={params["timestamp"]}&version={params["version"]}' - h = Hash(algorithm=MD5(), backend=default_backend()) - h.update(f'{params["method"]}?{data}{api_key}'.encode('utf-8')) - return h.finalize().hex() +def calculate_signature(params, secret): + sign_params = { + key: params[key] + for key in sorted(['access_token', 'appkey', 'method', 'param', 'signMethod', 'timestamp', 'version']) + if params.get(key) + } + sign_params['signSecret'] = secret + return base64.b64encode(hmac.new( + secret.encode(), + '&'.join(f'{k}={v}' for k, v in sign_params.items()).encode(), + hashlib.sha256 + ).digest()).decode() class Code(IntegerChoices): - FAIL = -1, '失败' - SUCCESS = 0, '成功' - INVALID_WHITE_LIST = 2051, 'ip 未在白名单' + SUCCESS = 1, '成功' class Result(BaseResult): - success: bool - error_code: Optional[int] + result: Optional[int] + error: Optional[str] error_msg: Optional[str] @@ -33,31 +39,36 @@ class Client(BaseClient): API_VERSION = 1 API_BASE_URL = 'https://openapi.kwaixiaodian.com' - def __init__(self, app_id, secret, message_key): + def __init__(self, app_id, secret, sign_secret, message_key): super().__init__() self.app_id = app_id self.secret = secret + self.sign_secret = sign_secret self.message_key = message_key self.codes = Code + self._token = Optional[Token] def request(self, method, endpoint, action=None, params=None, data=None, token_request=False): public_params = {} - if data is not None: + if (params is not None or data is not None) and token_request is False: public_params.update(**{ - 'appkey': self.app_id, 'timestamp': str(int(time.time() * 1000)), 'access_token': '', + 'appkey': self.app_id, 'timestamp': str(int(time.time() * 1000)), 'version': self.API_VERSION, 'method': action, 'signMethod': 'HMAC_SHA256' }) - public_params['sign'] = calculate_signature(public_params, self.secret) - public_params['param'] = None + public_params['access_token'] = self._token.access_token + public_params['param'] = json.dumps(params or data) + public_params['sign'] = calculate_signature(public_params, self.sign_secret) request_url = f'{self.API_BASE_URL}{endpoint}' - response = self._request(method, request_url, params=params, json=data) + response = self._request(method, request_url, params=public_params, json=data) + print(response.status_code, response.json()) return Result(**response.json()) def decrypt(self, message): - crypt = base64.b64decode(message) - key = base64.b64decode(self.message_key) - block = AES.new(key, AES.MODE_CBC, iv=bytes(AES.block_size)) - decrypt_data = block.decrypt(crypt) + block = AES.new( + base64.b64decode(self.message_key), + AES.MODE_CBC, iv=bytes(AES.block_size) + ) + decrypt_data = block.decrypt(base64.b64decode(message)) length = len(decrypt_data) return decrypt_data[:length - int(decrypt_data[length - 1])].decode() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/resources/kuaishou/order.detail.json b/tests/resources/kuaishou/order.detail.json new file mode 100644 index 0000000..4288f81 --- /dev/null +++ b/tests/resources/kuaishou/order.detail.json @@ -0,0 +1,167 @@ +{ + "result": 1, + "msg": "success", + "error_msg": "", + "code": "1", + "data": { + "addressUpdateAuditInfo": { + "timeoutOfAudit": 0, + "auditTime": 0, + "auditStatus": 0 + }, + "orderBaseInfo": { + "discountFee": 0, + "buyerNick": "黑黑森林", + "payTime": 1722414864126, + "orderLabels": [], + "remark": "", + "remindShipmentSign": 0, + "oid": 2421300093168518, + "sellerOpenId": "f1875c15a9540dfc14bf57240ecc19ea", + "expressFee": 0, + "orderSellerRoleInfo": { + "roleId": 3684946753, + "roleName": "叫我大王啊", + "roleType": 2 + }, + "buyerImage": "http://p1.a.yximgs.com/uhead/AB/2022/12/28/07/BMjAyMjEyMjgwNzU0MjVfMTIwNTM3NDUxOF8yX2hkNzg0XzMzMA==_s.jpg", + "shopNewBuyer": true, + "payType": 1, + "multiplePiecesNo": 0, + "enableSplitDeliveryOrder": false, + "validPromiseShipmentTimeStamp": 0, + "privacyInfoAuthTime": 0, + "sellerNick": "路飞学城", + "disableDeliveryReasonCode": [], + "recvTime": 1723021599587, + "buyerOpenId": "f1875c15a9540dfc14bf57249e0d4e74", + "cpsType": 2, + "promiseTimeStampOfDelivery": 0, + "refundTime": 0, + "carrierType": 501, + "orderTaxInfo": { + "sellerBearAmount": 0, + "taxAmount": 0 + }, + "platformNewBuyer": false, + "riskCode": 0, + "updateTime": 1723626518962, + "theDayOfDeliverGoodsTime": 120, + "commentStatus": 0, + "sendTime": 1722416798536, + "tradeInPayAfterPromoAmount": 0, + "preSale": 0, + "privacyInfoAuthStatus": 0, + "coType": 0, + "createTime": 1722414838006, + "totalFee": 3990, + "allActivityType": [ + 1 + ], + "sellerDelayPromiseTimeStamp": 0, + "payChannel": "WECHAT", + "remindShipmentTime": 0, + "activityType": 1, + "allowanceExpressFee": 0, + "carrierId": 139257659009, + "priorityDelivery": false, + "payChannelDiscount": 0, + "status": 70 + }, + "orderRefundList": [], + "orderAddress": { + "districtCode": 440303, + "town": "东门街道", + "city": "深圳市", + "townCode": 440303003, + "cityCode": 4403, + "provinceCode": 44, + "encryptedMobile": "$L+RK0vS4Rut6N/aKkQEBvMbNXun98O59RUfOzUY4m5o=$Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSIAe0aRLWaoD2oiwk2nlKEKTEFt57VBksaQglbyt6sC9GGhIZX5P7cVJH1E/Mo1JoBpwd4GYiIB0oyFIXH8yL3SAiNabYwkFbhrzSxLl6whBL2GKVFXUPKAUwAQ==&Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSMP17rjuOMBNrASUiXqunIyomj5MHedR1KJeJDCEB45RRxycMdnjx4CBvdTJLWsscyxoS84Ep+1oUdyK83WQgVLDRySNiIiCvotY9SUlIvsueix9Zzz+bnixOhoZzRRUq6i0+cQW90igFMAE=$1$$", + "encryptedConsignee": "#eLUfV/YgV/Yg#Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSIC/B8sJje507i7Xz4K4DsNy/edqATmg6D9UaBTDwkxSNGhI6OrPpYccMWaKKB5WNYVDcgJMiIBPthwM5VeHf2pb/KgAsAmY6ulCQysZsPCB+WWl31aiwKAUwAQ==&Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSMJ3b0sbvkChdv3uD/+l8kKDbndRrxz2YkLGLbnjuezMzPUwT3ssxjZDFYsFtYZkyxRoSbSjQJWPrl8eOyT19si3bYiXIIiDuWuEuyHpe5ThBiAANQpwrrs2cyqZ1bgE8NgL42hf6fCgFMAE=#1##", + "desensitiseConsignee": "韩**", + "encryptedAddress": "~vV0Zk2IQvA4kiUxKmBfWayfiUvOrdoCc7gkc5dY82NDK3bAH6sILlz932NDK~Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSQDcRTwk0KngwHclcGI8yS51PLsWiR/7kQKEPMVEGebUFD2f43/aPe+CvDYRnVmMtqvc0OKLbZkUQc32DCWOF+FcaEtldSMUcqQcJbeu9FtRcCQELnyIgTwP7EICUVH48yQBEv7S00t2dKcnAvSl+2Nz8kloGsbAoBTAB&Ci1tZXJjaGFudC5vcGVuLnNlY3JldC5rZXkua3M2Njg3ODQ1NDUxODg4MTg3MzMSMMYaiZ9dzouyh0PR809gJKU2++J7uNDqJymf2TfFf+xShskeKcDnqh9HZQBONE1ptRoSbDZf3bypVLaTa6g0UYt32DsBIiDDt1gbOyZNgVNiqpyZt3W0X+Nqa8hYAVo/PYzywwURAygFMAE=~1~~", + "province": "广东省", + "district": "罗湖区", + "desensitiseMobile": "1********18", + "desensitiseAddress": "**中路缤纷时代家园**栋****" + }, + "orderLogisticsInfo": [], + "orderItemInfo": { + "itemPicUrl": "https://u2-201.ecukwai.com/bs2/image-kwaishop-product/ITEM_IMAGE-3608795560-9cd916fc5afa43adb535b4285649652a.jpg", + "itemType": 1, + "discountFee": 0, + "originalPrice": 3990, + "itemTitle": "Python/Go/Java/Linux人工智能数据分析前端测试网络安全系列课程", + "orderItemId": 110100106327518, + "num": 1, + "itemExtra": { + "categoryInfo": { + "itemCid": 12727, + "categoryName": "IT职业/编程/计算机考试" + } + }, + "serviceInfo": { + "serviceRule": "{\"refundRule\":\"4\",\"depositRule\":\"\",\"deliverGoodsIntervalTime\":0,\"saleFlag\":true,\"promiseDeliveryTime\":-1,\"theDayOfDeliverGoodsTime\":0,\"immediatelyOnOfflineFlag\":0,\"deliveryMethod\":\"\",\"supportVerification\":-1,\"certMerchantCode\":\"\",\"certExpireType\":0,\"certStartTime\":0,\"certEndTime\":0,\"certExpDays\":0,\"orderPurchaseLimitType\":3,\"minOrderCount\":1,\"maxOrderCount\":1,\"servicePromise\":{\"freshRotRefund\":false,\"brokenRefund\":false,\"allergyRefund\":false,\"crabRefund\":false},\"unavailableTimeRule\":{\"weeks\":[],\"holidays\":[],\"timeRanges\":[]},\"qualityInspection\":-1,\"customerInfoType\":\"\",\"customerCertificateTypeList\":[],\"priceProtectDays\":0,\"deliveryTimeMode\":\"spot\",\"mixPromiseDeliveryTime\":-1,\"mixPresaleDeliveryTime\":[],\"serviceProviderPolicy\":{\"minAge\":0,\"maxAge\":0,\"days\":0,\"times\":0}}", + "freight": false, + "freightProviderType": 0, + "firstOrderGuarantee": false, + "instantDelivery": 0, + "instantRefund": false, + "serviceRuleInfo": { + "refundRule": "4", + "depositRule": "", + "deliverGoodsIntervalTime": 0, + "theDayOfDeliverGoodsTime": 0, + "saleFlag": true, + "promiseDeliveryTime": -1, + "immediatelyOnOfflineFlag": 0, + "deliveryMethod": "", + "supportVerification": -1, + "certMerchantCode": "", + "certExpireType": 0, + "certStartTime": 0, + "certEndTime": 0, + "certExpDays": 0 + }, + "freightProviderTypeDesc": "", + "showInsuranceRefuseTag": false, + "insuranceRefuseReason": "", + "freightProviderExplainDesc": "", + "buyExpensiveCompensateInfo": { + "canDisplayTag": false + } + }, + "goodStoreCode": 0, + "warehouseCode": "", + "itemId": 20897060322560, + "relItemId": 20897060322560, + "relSkuId": 0, + "price": 3990, + "itemLinkUrl": "https://app.kwaixiaodian.com/merchant/shop/detail?id=20897060322560&hyId=kwaishop&layoutType=4&promoteChannel=1&promoteId=3684946753", + "skuNick": "self001", + "skuDesc": "规格", + "goodsCode": "", + "skuId": 89217528485560 + }, + "orderDeliveryInfo": { + "deliveryNum": 1, + "enableAppendPackage": true, + "totalPackageNum": 0, + "maxPackageNum": 50, + "packageNum": 0 + }, + "orderFlag": { + "flag": [ + "grey_flag_tag_order" + ] + }, + "subOrderInfo": [], + "orderCpsInfo": { + "distributorName": "叫我大王啊", + "distributorId": 3684946753 + } + }, + "requestId": "723649481094130422", + "sub_msg": "", + "sub_code": "1" +}