├── tests └── __init__.py ├── requirements.txt ├── .gitignore ├── package.json ├── README.md ├── demos ├── python_demo.py ├── bmak_demo.py ├── veve_demo.py └── veve_graphql_demo.py ├── nodejs_demo.js └── unicornsdk.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | brotli 2 | loguru 3 | requests-futures 4 | python-dateutil 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | **/__pycache__/ 3 | /node_modules/ 4 | /build/* 5 | /dist/* 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^0.25.0", 4 | "concat-stream": "^2.0.0", 5 | "form-data": "^4.0.0", 6 | "needle": "^3.0.0", 7 | "node-gzip": "^1.1.2", 8 | "tunnel": "^0.0.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kpsdk 2 | Kasada (https://www.kasada.io/) ips.js 3 | 4 | keyword: nike snkrs headers x-kpsdk-ct x-kpsdk-cd x_kpsdk_st x_kpsdk_cr ak_bmsc_nke botman kpsdk bmak pow abck 5 | 6 | see: https://us.unicorn-bot.com/docs 7 | 8 | contact us: https://t.co/HWHs1FurZa 9 | 10 | discord: Ronaldinho#2456 / twitter: Unicorn59541103 11 | 12 | 13 | -------------------------------------------------------------------------------- /demos/python_demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import Optional, TYPE_CHECKING 5 | from enum import Enum 6 | 7 | import gzip 8 | import base64 9 | from requests_futures.sessions import FuturesSession 10 | from loguru import logger 11 | 12 | from unicornsdk import UnicornSDK 13 | 14 | def get_proxys(): 15 | cur_proxyuri = "http://127.0.0.1:8888" 16 | proxies = { 17 | "http": cur_proxyuri, 18 | "https": cur_proxyuri, 19 | } 20 | return proxies 21 | # return None 22 | 23 | async def asyncmain(): 24 | my_token = "TOKEN_FROM_LOGIN" 25 | sdk = UnicornSDK(token=my_token) 26 | with open("../tests/ips.js", "rb") as f: 27 | content = f.read() 28 | resp = await sdk.kpsdk_parse_ips(content, host="https://s3.nikecdn.com") 29 | for k, v in resp.items(): 30 | if k != "body": 31 | logger.debug(f"{k} --> {v}") 32 | 33 | 34 | 35 | if __name__ == '__main__': 36 | asyncio.run(asyncmain()) 37 | -------------------------------------------------------------------------------- /demos/bmak_demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import json 5 | import os 6 | import uuid 7 | from datetime import datetime, timedelta 8 | from pprint import pprint 9 | 10 | import urllib3 11 | from pathlib import Path 12 | 13 | urllib3.disable_warnings() 14 | 15 | from requests_futures.sessions import FuturesSession 16 | from loguru import logger 17 | import brotli 18 | 19 | from unicornsdk import UnicornSDK, PlatForm 20 | 21 | def print_sensordatas(sensor_datas): 22 | for i in sensor_datas: 23 | logger.debug(i) 24 | 25 | async def asyncmain(): 26 | my_token = os.getenv("MYTOKEN", "TOKEN_FROM_LOGIN") 27 | sdk = UnicornSDK(my_token) 28 | device = await sdk.init_session("testid", platform=PlatForm.WINDOWS) 29 | useragent = device["user_agent"] 30 | 31 | # TODO: reqyest the site and found the url for post sensor_data 32 | # for example 33 | current_url = "https://s3.nikecdn.com/unite/mobile.html?mid=87851713624057929094625979408669721627&androidSDKVersion=2.8.1&uxid=com.nike.commerce.snkrs.droid&locale=zh_CN&backendEnvironment=identity&view=login&clientId=qG9fJbnMcBPAMGibPRGI72Zr89l8CD4R" 34 | akamain_url="https://s3.nikecdn.com/hNtm01h5Ctyeil9cVIO0j-n8/Eaz7kND4Eu5N/PVQiZgE/cl4/EEiMGSCA" 35 | bmak = sdk.get_bmak(bmak_url=akamain_url) 36 | _abck = "xxxx" 37 | 38 | # TODO: request get bmak js 39 | 40 | # bmak js loaded 41 | sensor_datas = await bmak.bmak_sensordata(url=current_url) 42 | print_sensordatas(sensor_datas["sensor_datas"]) 43 | # TODO: post sensordatas 44 | await asyncio.sleep(1) 45 | _abck = "NEW_ABCK_COOKIE" 46 | 47 | 48 | # move mouse 49 | sensor_datas = await bmak.bmak_sensordata(url=current_url, move="short", _abck=_abck) 50 | print_sensordatas(sensor_datas["sensor_datas"]) 51 | # TODO: post sensordatas 52 | await asyncio.sleep(1) 53 | _abck = "NEW_ABCK_COOKIE" 54 | 55 | await asyncio.sleep(1) 56 | 57 | # click mouse 58 | sensor_datas = await bmak.bmak_sensordata(url=current_url, click=True, _abck=_abck) 59 | print_sensordatas(sensor_datas["sensor_datas"]) 60 | # TODO: post sensordatas 61 | _abck = "NEW_ABCK_COOKIE" 62 | 63 | 64 | 65 | if __name__ == '__main__': 66 | asyncio.run(asyncmain()) 67 | -------------------------------------------------------------------------------- /demos/veve_demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import os 5 | from datetime import datetime 6 | import urllib3 7 | 8 | urllib3.disable_warnings() 9 | 10 | from requests_futures.sessions import FuturesSession 11 | from loguru import logger 12 | import brotli 13 | 14 | from unicornsdk import UnicornSDK, PlatForm 15 | 16 | def now_time_ms(): 17 | return int(datetime.now().timestamp() * 1000) 18 | 19 | 20 | def get_proxys(): 21 | cur_proxyuri = "http://127.0.0.1:8888" 22 | proxies = { 23 | "http": cur_proxyuri, 24 | "https": cur_proxyuri, 25 | } 26 | return proxies 27 | # return None 28 | 29 | 30 | async def asyncmain(): 31 | my_token = os.getenv("MYTOKEN", "TOKEN_FROM_LOGIN") 32 | sdk = UnicornSDK(my_token) 33 | device = await sdk.init_session("testid", platform=PlatForm.ANDROID) 34 | useragent = device["user_agent"] 35 | 36 | 37 | orgin = "https://mobile.api.prod.veve.me" 38 | # orgin = "https://web.api.prod.veve.me" 39 | client = FuturesSession() 40 | fp_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp" 41 | resp = await asyncio.wrap_future(client.get(fp_url, headers={ 42 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 43 | "accept-encoding": "gzip, deflate, br", 44 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 45 | "user-agent": useragent, 46 | }, proxies=get_proxys(), verify=False)) 47 | 48 | # if True: 49 | if resp.status_code == 429: 50 | ct = resp.headers["x-kpsdk-ct"] 51 | # &x-kpsdk-v=i-1.4.0 52 | ips_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/ips.js?KP_UIDz={ct}" 53 | 54 | resp = await asyncio.wrap_future(client.get(ips_url, headers={ 55 | "accept": "*/*", 56 | "accept-encoding": "gzip, deflate, br", 57 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 58 | "referer": f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 59 | "user-agent": useragent, 60 | }, proxies=get_proxys(), verify=False,)) 61 | ipsjs = resp.content 62 | 63 | kpparam = await sdk.kpsdk_parse_ips(ipsjs, host=orgin) 64 | 65 | tl_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/tl" 66 | resp = await asyncio.wrap_future(client.post( 67 | tl_url, 68 | headers={ 69 | "accept": "*/*", 70 | "content-type": "application/octet-stream", 71 | "accept-encoding": "gzip, deflate, br", 72 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 73 | "referer": f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 74 | "origin": orgin, 75 | "user-agent": useragent, 76 | "x-kpsdk-ct": kpparam["x_kpsdk_ct"] 77 | }, 78 | data=kpparam["body"], 79 | proxies=get_proxys(), verify=False, 80 | )) 81 | 82 | logger.debug(resp.status_code) 83 | logger.debug(resp.text) 84 | 85 | x_kpsdk_ct = resp.headers["x-kpsdk-ct"] 86 | x_kpsdk_st = resp.headers["x-kpsdk-st"] 87 | st_diff = now_time_ms() - int(x_kpsdk_st) 88 | logger.debug(f"x_kpsdk_ct: {x_kpsdk_ct}") 89 | logger.debug(f"x_kpsdk_st: {x_kpsdk_st}") 90 | logger.debug(f"st_diff: {st_diff}") 91 | kpparam = await sdk.kpsdk_answer(x_kpsdk_ct, x_kpsdk_st, st_diff) 92 | logger.debug(kpparam) 93 | 94 | send_url = f"{orgin}/api/auth/totp/send" 95 | resp = await asyncio.wrap_future(client.post( 96 | send_url, 97 | headers={ 98 | "accept": "*/*", 99 | "content-type": "application/json", 100 | "accept-encoding": "gzip, deflate, br", 101 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 102 | "client-name": "veve-web-wallet", 103 | "client-version": "1.2.28", 104 | "referer": f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 105 | "origin": "https://omi.veve.me", 106 | "user-agent": useragent, 107 | "x-kpsdk-ct": kpparam["x_kpsdk_ct"], 108 | "x-kpsdk-cd": kpparam["x_kpsdk_cd"], 109 | }, 110 | json={"email":"xxx@yahoo.com"}, 111 | proxies=get_proxys(), verify=False, 112 | )) 113 | logger.debug(resp.status_code) 114 | logger.debug(resp.text) 115 | 116 | 117 | if __name__ == '__main__': 118 | asyncio.run(asyncmain()) 119 | -------------------------------------------------------------------------------- /nodejs_demo.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const axios = require("axios") 3 | const needle = require('needle'); 4 | const querystring = require('querystring'); 5 | const FormData = require('form-data'); 6 | const {gzip, ungzip} = require('node-gzip'); 7 | 8 | 9 | class UnicornSDK { 10 | constructor(token) { 11 | this.baseURL = "https://us.unicorn-bot.com" 12 | // this.baseURL = "http://127.0.0.1:8000" 13 | this.token = token 14 | this.CLIENT = axios.create({ 15 | baseURL: this.baseURL, 16 | headers: { 17 | "Authorization": this.getAuthorization() 18 | }, 19 | proxy: this.getProxyForSDK(), 20 | }) 21 | this.XSESSIONDATA = null 22 | } 23 | 24 | getToken() { 25 | return this.token 26 | } 27 | 28 | getAuthorization() { 29 | return "Bearer " + this.getToken() 30 | } 31 | 32 | getProxyForSDK() { 33 | return { 34 | host: "127.0.0.1", 35 | port: 8888, 36 | } 37 | } 38 | 39 | getProxyForSDKSimple() { 40 | return "http://127.0.0.1:8888" 41 | } 42 | 43 | find_cookie(cookies, name) { 44 | for (let idx in cookies) { 45 | let cookie = cookies[idx] 46 | let kv = cookie.split(";")[0] 47 | let m = kv.match(/(\w+)\=(\S+)/) 48 | if (m) { 49 | let k = m[1] 50 | let v = m[2] 51 | if (k === name) { 52 | return v 53 | } 54 | } 55 | } 56 | } 57 | 58 | async init_session(sessionid, platform = "ANDROID") { 59 | let resp = await needle( 60 | "post", 61 | this.baseURL + "/api/session/init/", 62 | { 63 | "sessionid": sessionid, 64 | "platform": platform, 65 | }, 66 | { 67 | headers: { 68 | "Authorization": this.getAuthorization() 69 | }, 70 | proxy: this.getProxyForSDKSimple(), 71 | json: true, 72 | follow_set_cookies: true 73 | } 74 | ) 75 | if (resp.statusCode != 200) { 76 | throw Error(`init_session --> ${resp.statusCode} --> ${JSON.stringify(resp.body)}`) 77 | } 78 | 79 | this.XSESSIONDATA = resp.cookies["XSESSIONDATA"] 80 | console.debug("XSESSIONDATA:", this.XSESSIONDATA) 81 | return resp.body 82 | } 83 | 84 | async init_session_axios(sessionid, platform = "ANDROID") { 85 | try { 86 | let resp = await this.CLIENT.post( 87 | "/api/session/init/", 88 | { 89 | "sessionid": sessionid, 90 | "platform": platform, 91 | }, 92 | ) 93 | 94 | this.XSESSIONDATA = this.find_cookie(resp.headers["set-cookie"], "XSESSIONDATA") 95 | console.debug("XSESSIONDATA:", this.XSESSIONDATA) 96 | return resp.data 97 | } catch (e) { 98 | let resp = e.response 99 | throw Error(`init_session --> ${resp.status}: ${resp.statusText}`) 100 | } 101 | } 102 | 103 | async parse_ips(host, ipsjs) { 104 | let params = { 105 | "kpver": "v20210513", 106 | "host": host, 107 | "site": "VEVE", 108 | "compress_method": "GZIP", 109 | } 110 | var form = { 111 | ips_js: { 112 | buffer: ipsjs, 113 | filename: 'ips_js', 114 | content_type: 'application/octet-stream' 115 | } 116 | } 117 | let resp = await needle( 118 | "post", 119 | this.baseURL + "/api/kpsdk/ips/" + "?" + querystring.stringify(params), 120 | form, 121 | { 122 | headers: { 123 | "Authorization": this.getAuthorization() 124 | }, 125 | 'cookies': { 126 | XSESSIONDATA: this.XSESSIONDATA, 127 | }, 128 | multipart: true, 129 | proxy: this.getProxyForSDKSimple(), 130 | } 131 | ) 132 | } 133 | 134 | async parse_ips_axios(host, ipsjs) { 135 | try { 136 | var formData = new FormData(); 137 | formData.append("ips_js", ipsjs, {filename:"ips_js"}) 138 | let headers = formData.getHeaders() 139 | headers["Cookie"] = `XSESSIONDATA=${this.XSESSIONDATA}` 140 | let resp = await this.CLIENT.post( 141 | "/api/kpsdk/ips/", 142 | formData, 143 | { 144 | headers: headers, 145 | params: { 146 | "kpver": "v20210513", 147 | "host": host, 148 | "site": "VEVE", 149 | "compress_method": "GZIP", 150 | } 151 | } 152 | ) 153 | } catch (e) { 154 | console.error(JSON.stringify(e)) 155 | let resp = e.response 156 | throw Error(`init_session --> ${resp.status}: ${resp.statusText}, ${resp.data}`) 157 | } 158 | 159 | } 160 | } 161 | 162 | 163 | console.log("hello world!") 164 | 165 | const startDemo = async () => { 166 | const my_token = "TOKEN_FROM_LOGIN" 167 | let sdk = new UnicornSDK(my_token) 168 | 169 | resp = await sdk.init_session("testid") 170 | console.log(resp) 171 | 172 | let ua = resp["user_agent"] 173 | 174 | var ipsjs = fs.readFileSync('./tests/ips.js', 'utf8') 175 | const gzipd_ipsjs = await gzip(ipsjs) 176 | 177 | kpparam = await sdk.parse_ips("https://mobile.api.prod.veve.me", gzipd_ipsjs) 178 | } 179 | 180 | startDemo().then(res => { 181 | console.log("Yes!") 182 | }).catch(err => { 183 | console.error(err) 184 | }) 185 | -------------------------------------------------------------------------------- /unicornsdk.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | from typing import Optional, TYPE_CHECKING 5 | from enum import Enum 6 | 7 | import urllib3 8 | urllib3.disable_warnings() 9 | 10 | import gzip 11 | import base64 12 | from datetime import datetime 13 | from requests_futures.sessions import FuturesSession 14 | import requests 15 | from dateutil import parser 16 | import configparser 17 | from loguru import logger 18 | 19 | 20 | def parse_from_iso8601_to_local(dt_str): 21 | if not dt_str: 22 | return None 23 | return parser.isoparse(dt_str).astimezone() 24 | 25 | def now_time_s(): 26 | return int(datetime.now().timestamp()) 27 | 28 | 29 | 30 | class PlatForm(str, Enum): 31 | WINDOWS = "WINDOWS" 32 | ANDROID = "ANDROID" 33 | IOS = "IOS" 34 | OSX = "OSX" 35 | 36 | 37 | class KpsdkVersion(str, Enum): 38 | v20210513 = "v20210513" 39 | v202107 = "v202107" 40 | 41 | 42 | class UnicornSDK: 43 | AUTH = { 44 | "access_token": None 45 | } 46 | CFG = configparser.ConfigParser(interpolation=None) 47 | 48 | def __init__(self, token=None, api_url="https://us.unicorn-bot.com"): 49 | self._api_url = api_url 50 | # self._api_url = "http://127.0.0.1:9000" 51 | self._CLIENT = FuturesSession() 52 | self._XSESSIONDATA = None 53 | self._token = token 54 | self.device_info = None 55 | 56 | def get_proxys_for_sdk(self): 57 | cur_proxyuri = "http://127.0.0.1:8888" 58 | proxies = { 59 | "http": cur_proxyuri, 60 | "https": cur_proxyuri, 61 | } 62 | return proxies 63 | # return None 64 | 65 | def _get_client(self): 66 | return self._CLIENT 67 | 68 | def get_authorization(self): 69 | return { 70 | "Authorization": "Bearer " + self.access_token 71 | } 72 | 73 | @property 74 | def XSESSIONDATA(self): 75 | """ 76 | get back the XSESSIONDATA for save 77 | :return: 78 | """ 79 | return self._XSESSIONDATA 80 | 81 | @XSESSIONDATA.setter 82 | def XSESSIONDATA(self, v): 83 | self._XSESSIONDATA = v 84 | 85 | @property 86 | def access_token(self): 87 | return self._token or self.AUTH.get("access_token") 88 | 89 | @property 90 | def api_url(self): 91 | return self._api_url or self.CFG.get("api_url") 92 | 93 | @classmethod 94 | def load_settings(cls, CFG_PATH): 95 | cls.CFG.read(CFG_PATH) 96 | 97 | @classmethod 98 | def save_settings(cls, CFG_PATH): 99 | with open(CFG_PATH, "w", encoding="utf8") as f: 100 | cls.CFG.write(f) 101 | 102 | @classmethod 103 | def check_auth(cls, CFG_PATH): 104 | cls.load_settings(CFG_PATH) 105 | 106 | settings = cls.CFG 107 | do_login = False 108 | if not settings["auth"].get("access_token"): 109 | do_login = True 110 | 111 | if not settings["auth"].get("refresh_token_expiration") or parse_from_iso8601_to_local( 112 | settings["auth"]["refresh_token_expiration"]).timestamp() - now_time_s() < 3600 * 24: 113 | do_login = True 114 | 115 | # 剩余时长小于一小时直接刷新一个 116 | if not settings["auth"].get("access_token_expiration") or parse_from_iso8601_to_local( 117 | settings["auth"]["access_token_expiration"]).timestamp() - now_time_s() < 3600: 118 | do_login = True 119 | 120 | if do_login: 121 | if not settings["auth"].get("username") or not settings["auth"].get("password"): 122 | raise Exception("missing auth info!") 123 | cls.login(settings) 124 | 125 | cls.set_auth(access_token=settings["auth"]["access_token"]) 126 | cls.save_settings(CFG_PATH) 127 | 128 | @classmethod 129 | def login(cls, settings=None): 130 | assert settings 131 | assert settings["auth"]["username"] 132 | assert settings["auth"]["password"] 133 | param = { 134 | "username": settings["auth"]["username"], 135 | # "email": settings["auth"].get("email"), 136 | "password": settings["auth"]["password"], 137 | } 138 | api_url = settings["auth"]["api_url"] 139 | resp = requests.post(api_url + "/auth/login/", json=param, timeout=30) 140 | 141 | if resp.status_code == 200: 142 | j = resp.json() 143 | settings["auth"]["access_token"] = j["access_token"] 144 | settings["auth"]["refresh_token"] = j["refresh_token"] 145 | settings["auth"]["access_token_expiration"] = j["access_token_expiration"] 146 | settings["auth"]["refresh_token_expiration"] = j["refresh_token_expiration"] 147 | else: 148 | raise Exception(f"Login Faild! {resp.text}") 149 | 150 | @classmethod 151 | def set_auth(cls, *, access_token): 152 | cls.AUTH["access_token"] = access_token 153 | 154 | 155 | async def init_session(self, sessionid: str, platform: PlatForm, locale="zh_CN"): 156 | url = self.api_url + "/api/session/init/" 157 | params = { 158 | "sessionid": sessionid, 159 | "platform": platform, 160 | "locale": locale, 161 | } 162 | headers = self.get_authorization() 163 | resp = await asyncio.wrap_future(self._get_client().post(url, json=params, headers=headers, verify=False, proxies=self.get_proxys_for_sdk())) 164 | if resp.status_code != 200: 165 | raise Exception(resp.text) 166 | self._XSESSIONDATA = resp.cookies["XSESSIONDATA"] 167 | self.device_info = resp.json() 168 | return self.device_info 169 | 170 | def load_state(self, bundle): 171 | self._XSESSIONDATA = bundle.get("XSESSIONDATA") 172 | self.device_info = bundle.get("device_info") 173 | 174 | def save_state(self, bundle): 175 | bundle["XSESSIONDATA"] = self._XSESSIONDATA 176 | bundle["device_info"] = self.device_info 177 | 178 | 179 | async def kpsdk_parse_ips(self, ips_content, *, ver=KpsdkVersion.v202107, host, site=None, compress_method="GZIP", 180 | proxy_uri=None, cookie=None, cookiename=None): 181 | try: 182 | gzipjps = gzip.compress(ips_content) 183 | client = self._get_client() 184 | param = { 185 | "kpver": ver.value, 186 | "host": host, 187 | "site": site, 188 | "proxy_uri": proxy_uri, 189 | "compress_method": compress_method, 190 | "cookie": cookie, 191 | "cookiename": cookiename, 192 | } 193 | 194 | resp = await asyncio.wrap_future(client.post(self.api_url + "/api/kpsdk/ips/", params=param, headers={ 195 | "Authorization": "Bearer " + self.access_token 196 | }, cookies={ 197 | "XSESSIONDATA": self.XSESSIONDATA 198 | }, files={"ips_js": gzipjps}, verify=False, proxies=self.get_proxys_for_sdk())) 199 | 200 | if resp.status_code == 200: 201 | kpparam = resp.json() 202 | tl_body_b64 = kpparam.get("tl_body_b64") 203 | if tl_body_b64: 204 | body = gzip.decompress(base64.b64decode(tl_body_b64)) 205 | kpparam["body"] = body 206 | return kpparam 207 | elif resp.status_code == 403: 208 | raise Exception("Not Authenticated") 209 | else: 210 | logger.error(resp.text) 211 | raise Exception(resp.text) 212 | except Exception as e: 213 | logger.error(repr(e)) 214 | raise e 215 | 216 | async def kpsdk_answer(self, x_kpsdk_ct, x_kpsdk_st, st_diff, x_kpsdk_cr=True): 217 | try: 218 | param = { 219 | "x_kpsdk_ct": x_kpsdk_ct, 220 | "x_kpsdk_cr": x_kpsdk_cr, 221 | "x_kpsdk_st": x_kpsdk_st, 222 | "st_diff": st_diff, 223 | } 224 | client = self._get_client() 225 | resp = await asyncio.wrap_future(client.post(self.api_url + "/api/kpsdk/answer/", headers={ 226 | "Authorization": "Bearer " + self.access_token 227 | }, cookies={ 228 | "XSESSIONDATA": self.XSESSIONDATA 229 | }, json=param, verify=False, proxies=self.get_proxys_for_sdk())) 230 | 231 | if resp.status_code == 200: 232 | kpparam = resp.json() 233 | return kpparam 234 | elif resp.status_code == 403: 235 | raise Exception("Not Authenticated") 236 | else: 237 | logger.error(resp.text) 238 | raise Exception(resp.text) 239 | except Exception as e: 240 | logger.error(repr(e)) 241 | raise e 242 | 243 | def get_bmak(self, bmak_url, timezoneoffset=None): 244 | return Bmak( 245 | bmak_url=bmak_url, 246 | timezoneoffset=timezoneoffset, 247 | sdk=self 248 | ) 249 | 250 | 251 | async def bmak_sensordata( 252 | self, *, 253 | forminfo="", forminfo_cns="", url=None, bmak_url=None, _abck=None, newpage=False, 254 | timezoneoffset=None, input=None, element=None, element_type=None, click=False, move=None): 255 | 256 | try: 257 | param = { 258 | "_abck": _abck, 259 | "newpage": newpage, 260 | "input": input, 261 | "element": element, 262 | "element_type": element_type, 263 | "click": click, 264 | "move": move, 265 | } 266 | if url is not None: 267 | param["url"] = url 268 | if bmak_url: 269 | param["bmak_url"] = bmak_url 270 | if forminfo is not None: 271 | param["forminfo"] = forminfo 272 | if forminfo_cns is not None: 273 | param["forminfo_cns"] = forminfo_cns 274 | if timezoneoffset: 275 | param["timezoneoffset"] = timezoneoffset 276 | 277 | client = self._get_client() 278 | resp = await asyncio.wrap_future(client.post(self.api_url + "/api/bmak/sensordata/", json=param, headers={ 279 | "Authorization": "Bearer " + self.access_token 280 | }, cookies={ 281 | "XSESSIONDATA": self.XSESSIONDATA 282 | }, verify=False, proxies=self.get_proxys_for_sdk())) 283 | 284 | if resp.status_code == 200: 285 | return resp.json() 286 | elif resp.status_code == 403: 287 | raise Exception("Not Authenticated") 288 | else: 289 | logger.error(resp.text) 290 | raise Exception(resp.text) 291 | except Exception as e: 292 | logger.error(repr(e)) 293 | raise e 294 | 295 | class Bmak: 296 | 297 | def __init__(self, 298 | bmak_url, 299 | timezoneoffset=None, 300 | sdk:UnicornSDK=None): 301 | self.sdk = sdk 302 | self.bmak_url=bmak_url 303 | self.timezoneoffset = timezoneoffset 304 | self.newpage = True 305 | 306 | def ab(self, str): 307 | """ 308 | hash element name or id 309 | """ 310 | if not str: 311 | return -1 312 | try: 313 | t = 0 314 | for r in str: 315 | n = ord(r) 316 | if n < 128: 317 | t += n 318 | return t 319 | except Exception as e: 320 | logger.error("ab() failed! " + str) 321 | return -2 322 | 323 | async def bmak_sensordata( 324 | self, *, 325 | url, 326 | _abck=None, 327 | input:str=None, element=None, element_type=None, 328 | click=False, 329 | move=None, 330 | forminfo="", forminfo_cns="", 331 | 332 | ): 333 | """ 334 | gen sensor from api 335 | :param url: current page url 336 | :param _abck: current _abck cookie 337 | :param input: generate keyboard event for the str, when use input, you should also set element / element_type 338 | :param element: the name or id or the input element 339 | :param element_type: only need when you want to generate input password event, set it to ”password“ 340 | :param click: generate click event 341 | :param move: generate mouse move event, set to "short" / "long“ 342 | :param forminfo: 343 | :param forminfo_cns: 344 | :return: 345 | """ 346 | 347 | ret = await self.sdk.bmak_sensordata( 348 | url=url, 349 | bmak_url=self.bmak_url, 350 | timezoneoffset=self.timezoneoffset, 351 | newpage=self.newpage, 352 | input=input, element=element, element_type=element_type, 353 | click=click, 354 | move=move, 355 | forminfo=forminfo, forminfo_cns=forminfo_cns 356 | ) 357 | self.newpage=False 358 | return ret 359 | -------------------------------------------------------------------------------- /demos/veve_graphql_demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import json 5 | import os 6 | import uuid 7 | from datetime import datetime, timedelta 8 | import urllib3 9 | from pathlib import Path 10 | import sys 11 | 12 | urllib3.disable_warnings() 13 | 14 | from requests_futures.sessions import FuturesSession 15 | from loguru import logger 16 | import brotli 17 | 18 | from unicornsdk import UnicornSDK, PlatForm 19 | 20 | 21 | def now_time_ms(): 22 | return int(datetime.now().timestamp() * 1000) 23 | 24 | 25 | class Veve: 26 | """ 27 | Ios version demo 28 | """ 29 | 30 | def __init__(self, sessionid, *, token, platform=PlatForm.IOS, 31 | origin="https://mobile.api.prod.veve.me"): 32 | self.sessionid = sessionid 33 | self.platform = platform 34 | self.x_kpsdk_ct = None 35 | self.x_kpsdk_st = None 36 | self.st_diff = None 37 | self.x_kpsdk_ct_expiretime = None 38 | self.orgin = origin 39 | self.sdk = UnicornSDK(token=token) 40 | self.client = FuturesSession() 41 | self.useragent = None 42 | self.x_kpsdk_v = "i-1.6.0" 43 | self.client_version = "1.0.555" 44 | self.client_name = "veve-app-ios" 45 | self.client_model = "iphone 11 pro max" 46 | self.client_user_id = str(uuid.uuid4()) 47 | self.client_id = str(uuid.uuid4()) 48 | self._login_cookie = None 49 | 50 | async def init_sesion(self): 51 | deviceinfo = await self.sdk.init_session(self.sessionid, platform=self.platform) 52 | self.useragent = deviceinfo["user_agent"] 53 | 54 | def get_proxys(self): 55 | cur_proxyuri = "http://127.0.0.1:8888" 56 | proxies = { 57 | "http": cur_proxyuri, 58 | "https": cur_proxyuri, 59 | } 60 | return proxies 61 | # return None 62 | 63 | def get_login_cookie(self): 64 | """ 65 | the resp cookie when you logined 66 | :return: 67 | """ 68 | # return ";".join([ 69 | # "veve=xxxxxxxxxxxxx", 70 | # "Domain=web.api.prod.veve.me", 71 | # "Path=/", 72 | # "HttpOnly", 73 | # "Secure", 74 | # "SameSite=None, KP_UIDz-ssn=xxxxxxxx", 75 | # "Max-Age=86400", 76 | # "Path=/", 77 | # "Expires=Wed, 02 Feb 2022 21:56:28 GMT", 78 | # "HttpOnly", 79 | # "Secure", 80 | # "SameSite=None, KP_UIDz=xxxxxxxx", 81 | # "Max-Age=86400", 82 | # "Path=/", 83 | # "Expires=Wed, 02 Feb 2022 21:56:28 GMT", 84 | # "HttpOnly" 85 | # ]) 86 | return self._login_cookie 87 | 88 | async def req_fp(self): 89 | orgin = self.orgin 90 | useragent = self.useragent 91 | fp_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp" 92 | resp = await asyncio.wrap_future(self.client.get(fp_url, headers={ 93 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 94 | "accept-encoding": "gzip, deflate, br", 95 | "x-kpsdk-v": self.x_kpsdk_v, 96 | "accept-language": "en-us", 97 | "user-agent": useragent, 98 | }, proxies=self.get_proxys(), verify=False)) 99 | return resp 100 | 101 | async def do_web_ips(self): 102 | orgin = self.orgin 103 | useragent = self.useragent 104 | fp_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp" 105 | resp = await self.req_fp() 106 | 107 | # if True: 108 | if resp.status_code == 429: 109 | ct = resp.headers["x-kpsdk-ct"] 110 | # &x-kpsdk-v=i-1.4.0 111 | ips_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3" \ 112 | f"/ips.js?KP_UIDz={ct}&x-kpsdk-v={self.x_kpsdk_v}" 113 | 114 | resp = await asyncio.wrap_future(self.client.get(ips_url, headers={ 115 | "accept": "*/*", 116 | "accept-encoding": "gzip, deflate, br", 117 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 118 | "referer": f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 119 | "user-agent": useragent, 120 | }, proxies=self.get_proxys(), verify=False)) 121 | ipsjs = resp.content 122 | 123 | kpparam = await self.sdk.kpsdk_parse_ips(ipsjs, host=orgin) 124 | 125 | tl_url = f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/tl" 126 | resp = await asyncio.wrap_future(self.client.post( 127 | tl_url, 128 | headers={ 129 | "accept": "*/*", 130 | "content-type": "application/octet-stream", 131 | "accept-encoding": "gzip, deflate, br", 132 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 133 | "referer": f"{orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 134 | "origin": orgin, 135 | "x-kpsdk-v": self.x_kpsdk_v, 136 | "user-agent": useragent, 137 | "x-kpsdk-ct": kpparam["x_kpsdk_ct"] 138 | }, 139 | data=kpparam["body"], 140 | proxies=self.get_proxys(), verify=False, 141 | )) 142 | 143 | logger.debug(resp.status_code) 144 | logger.debug(resp.text) 145 | 146 | x_kpsdk_ct = resp.headers["x-kpsdk-ct"] 147 | x_kpsdk_st = resp.headers["x-kpsdk-st"] 148 | st_diff = now_time_ms() - int(x_kpsdk_st) 149 | logger.debug(f"x_kpsdk_ct: {x_kpsdk_ct}") 150 | logger.debug(f"x_kpsdk_st: {x_kpsdk_st}") 151 | logger.debug(f"st_diff: {st_diff}") 152 | 153 | self.x_kpsdk_st = x_kpsdk_st 154 | self.x_kpsdk_ct = x_kpsdk_ct 155 | self.st_diff = st_diff 156 | self.x_kpsdk_ct_expiretime = (datetime.now() + timedelta(hours=22)).timestamp() 157 | 158 | elif resp.status_code == 200: 159 | raise Exception("please get ct from resp!") 160 | else: 161 | raise Exception("???") 162 | 163 | async def get_answer(self): 164 | kpparam = await self.sdk.kpsdk_answer(self.x_kpsdk_ct, self.x_kpsdk_st, self.st_diff) 165 | logger.debug(kpparam) 166 | return kpparam 167 | 168 | def refresh_ct(self, resp): 169 | ct = resp.headers.get("x-kpsdk-ct") 170 | if ct: 171 | logger.debug(f"ct --> {ct}") 172 | self.x_kpsdk_ct = ct 173 | self.x_kpsdk_ct_expiretime = (datetime.now() + timedelta(hours=22)).timestamp() 174 | 175 | async def req_post_api_auth_totp_send(self, email="xxx@yahoo.com"): 176 | send_url = f"{self.orgin}/api/auth/totp/send" 177 | kpparam = self.get_answer() 178 | resp = await asyncio.wrap_future(self.client.post( 179 | send_url, 180 | headers={ 181 | "accept": "*/*", 182 | "content-type": "application/json", 183 | "accept-encoding": "gzip, deflate, br", 184 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 185 | "client-name": "veve-web-wallet", 186 | "client-version": "1.2.9", 187 | "referer": f"{self.orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 188 | "origin": "https://omi.veve.me", 189 | "user-agent": self.useragent, 190 | "x-kpsdk-ct": kpparam["x_kpsdk_ct"], 191 | "x-kpsdk-cd": kpparam["x_kpsdk_cd"], 192 | }, 193 | json={"email": email}, 194 | proxies=self.get_proxys(), verify=False, 195 | )) 196 | logger.debug(resp.status_code) 197 | logger.debug(resp.text) 198 | return resp 199 | 200 | async def req_post_graphql(self, ql, client_operation="MarketMultiplesQuery"): 201 | url = f"{self.orgin}/graphql" 202 | kpparam = await self.get_answer() 203 | client = FuturesSession() 204 | 205 | headers = { 206 | "accept": "*/*", 207 | "accept-encoding": "gzip, deflate, br", 208 | "content-type": "application/json", 209 | "client-version": self.client_version, 210 | # "referer": f"{self.orgin}/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/fp", 211 | "origin": "https://omi.veve.me", 212 | "user-agent": self.useragent, 213 | "x-kpsdk-ct": kpparam["x_kpsdk_ct"], 214 | "x-kpsdk-cd": kpparam["x_kpsdk_cd"], 215 | "client-operation": client_operation, 216 | "client-name": self.client_name, 217 | "client-model": self.client_model, 218 | "client-brand": "apple", 219 | "client-manufacturer": "apple", 220 | "client-user-id": self.client_user_id, 221 | "client-id": self.client_id, 222 | "accept-language": "zh-cn", 223 | "x-kpsdk-v": self.x_kpsdk_v, 224 | "client-installer": "appstore", 225 | "client-carrier": "unknown", 226 | } 227 | if self.get_login_cookie(): 228 | headers["cookie"] = self.get_login_cookie() 229 | 230 | resp = await asyncio.wrap_future(client.post( 231 | url, 232 | headers=headers, 233 | data=ql, 234 | proxies=self.get_proxys(), verify=False, 235 | )) 236 | logger.debug(resp.status_code) 237 | logger.debug(resp.text) 238 | self.refresh_ct(resp) 239 | return resp 240 | 241 | def save_session(self): 242 | obj = { 243 | "x_kpsdk_ct": self.x_kpsdk_ct, 244 | "x_kpsdk_st": self.x_kpsdk_st, 245 | "st_diff": self.st_diff, 246 | "x_kpsdk_ct_expiretime": self.x_kpsdk_ct_expiretime, 247 | "login_cookie": self._login_cookie, 248 | "client_user_id": self.client_user_id, 249 | "client_id": self.client_id, 250 | } 251 | self.sdk.save_state(obj) 252 | with open(self.sessionid + ".json", "w", encoding="utf8") as f: 253 | json.dump(obj, f, indent=4, ensure_ascii=False) 254 | return obj 255 | 256 | async def refresh_ct_if_need(self): 257 | if self.x_kpsdk_ct and self.x_kpsdk_ct_expiretime and datetime.now().timestamp() < self.x_kpsdk_ct_expiretime: 258 | return 259 | logger.debug("we need refresh the ct from ips ...") 260 | await self.do_web_ips() 261 | 262 | def load_session(self): 263 | file = Path(self.sessionid + ".json") 264 | if file.exists(): 265 | with open(file, encoding="utf8") as f: 266 | obj = json.load(f) 267 | self.client_user_id = obj.get("client_user_id") 268 | self.client_id = obj.get("client_id") 269 | self.x_kpsdk_ct = obj.get("x_kpsdk_ct") 270 | self.x_kpsdk_st = obj.get("x_kpsdk_st") 271 | self.st_diff = obj.get("st_diff") 272 | self.x_kpsdk_ct_expiretime = obj.get("x_kpsdk_ct_expiretime") 273 | self._login_cookie = obj.get("login_cookie") 274 | self.sdk.load_state(obj) 275 | self.useragent = self.sdk.device_info["user_agent"] 276 | return obj 277 | return None 278 | 279 | 280 | async def test_if_ct_could_last_long(veve): 281 | """ 282 | since i-1.6.0 update, test if our ct could last for a long time and not got empty respose 283 | :param veve: 284 | :return: 285 | """ 286 | for idx, i in enumerate(range(10)): 287 | resp = await veve.req_post_graphql(r"""{ 288 | "operationName": "MarketMultiplesQuery", 289 | "variables": { 290 | "filterOptions": { 291 | "collectibleTypeId": "be660c37-ee7b-4da4-84ba-2138788889cd", 292 | "status": ["OPEN"], 293 | "type": "FIXED" 294 | }, 295 | "sortOptions": { 296 | "sortBy": "PRICE", 297 | "sortDirection": "ASCENDING" 298 | } 299 | }, 300 | "query": "query MarketMultiplesQuery($filterOptions: MarketListingFilter, $sortOptions: MarketListingSort!, $cursor: String) {\n marketListingList(\n first: 30\n after: $cursor\n filterOptions: $filterOptions\n sortOptions: $sortOptions\n ) {\n pageInfo {\n endCursor\n hasNextPage\n __typename\n }\n edges {\n node {\n id\n endingAt\n listingType\n userBidPosition\n bids {\n totalCount\n __typename\n }\n currentPrice\n seller {\n id\n username\n avatar {\n id\n url\n medResolutionUrl\n __typename\n }\n __typename\n }\n element {\n ... on Collectible {\n id\n issueNumber\n collectibleType {\n id\n totalIssued\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n" 301 | }""") 302 | if len(resp.text) == 0: 303 | break 304 | 305 | await asyncio.sleep(2) 306 | 307 | 308 | async def test_AppMetaInfo(veve): 309 | resp = await veve.req_post_graphql(r"""{ 310 | "operationName": "AppMetaInfo", 311 | "variables": { 312 | "client": "IOS" 313 | }, 314 | "query": "query AppMetaInfo($client: SupportedClients!) { minimumVersion(client: $client) featureFlagList { name enabled __typename }}" 315 | }""", client_operation="AppMetaInfo") 316 | 317 | 318 | async def asyncmain(): 319 | my_token = os.getenv("MYTOKEN", "TOKEN_FROM_LOGIN") 320 | veve = Veve("testid", token=my_token) 321 | 322 | if (not veve.load_session()): 323 | await veve.init_sesion() 324 | 325 | await veve.refresh_ct_if_need() 326 | 327 | # test get AppMetaInfo 328 | await test_AppMetaInfo(veve) 329 | # await test_if_ct_could_last_long(veve) 330 | 331 | veve.save_session() 332 | 333 | 334 | if __name__ == '__main__': 335 | asyncio.run(asyncmain()) 336 | --------------------------------------------------------------------------------