import argparse import os import sys import threading import urllib.parse import webbrowser from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path from typing import Literal from urllib.parse import urlparse, parse_qs import requests import toml from hypy_utils import json_stringify from hypy_utils.serializer import pickle_decode, pickle_encode from dotdotbuy_auth import sign_params, sign_api from popos.gateway_order import * from popos.popos import * r = requests.Session() r.encoding = 'utf-8' r.headers.update({ 'User-Agent': 'Superbuy/5530001 CFNetwork/1220.1 Darwin/20.3.0', 'app-key': 'aaa', }) MIN_DATE = '2023-01-01' CONFIG_PATH = Path('.config/superbuy') def user_id() -> str: return r.headers['userId'] def login(user: str, passwd: str) -> LoginData: # Desktop: # resp: SuperbuyResponse = jsn(r.post('https://login.superbuy.com/api/site/login', data={'login_name': user, 'password': passwd})) # assert resp.state == 0, resp # Mobile: resp: SuperbuyResponse = jsn(r.post('https://api.superbuy.com/auth/login', data={'loginToken': user, 'password': passwd})) assert resp.state == 0, resp data: LoginData = resp.data # Add mobile access token r.headers.update({'accessToken': data.accessToken}) r.headers.update({'userId': str(data.userId)}) print(f'Successfully logged in as {user}') return data def login_cached(user: str, passwd: str): global r p = CONFIG_PATH / f'ss_{user.replace("@", "_").replace(".", "_")}.pickle' if p.is_file(): print(f'Using cached login session for {user}') r = pickle_decode(p.read_bytes()) return login(user, passwd) CONFIG_PATH.mkdir(exist_ok=True, parents=True) p.write_bytes(pickle_encode(r)) def app_order_list(): # Mobile endpoint = 'https://front.superbuy.com/package/parcel/app-list' req = r.get(endpoint, params=dict(pageSize=100, currPage=1, includeOverdueItemFlg=1), headers=sign_params(dict(r.headers))) resp: SuperbuyResponse = jsn(req) return resp def gateway_order_list(limit: int = 200, type: Literal['storage', 'payment', 'all', 'receive'] = 'all', orderType: int = 0) -> list[GatewayOrder]: """ Get a list of orders that are already added to the account. """ # Mobile endpoint = f'https://api.superbuy.com/gateway/orderpkg/list/{user_id()}/1/{limit}' print(endpoint) print(sign_api(dict(r.headers))) req = r.get(endpoint, params=dict(type=type, orderType=orderType), headers=sign_api(dict(r.headers))) resp: PaginatedResponse = jsn(req) assert resp.Code == 10000, resp return resp.List def get_url_param(url: str, param: str) -> str: return parse_qs(urlparse(url).query)[param][0] def crawl(item_url: str) -> dict: id = get_url_param(item_url, 'id') out_path = Path(f'crawler/{id}.json') if out_path.is_file(): return json.loads(out_path.read_text()) while True: print(f'Crawling {id}...') out_path.parent.mkdir(parents=True, exist_ok=True) resp = r.post('https://front.superbuy.com/crawler/', data={"needSoldOutSkuInfo": 1, "location": 2, "goodUrl": item_url}) if resp.status_code != 200: print(f"Request failed: {resp.status_code}") assert input("Retry? [y/N]").lower() == 'y' continue out_path.write_text(resp.text) return resp.json() def create_diy_order(create: list[TaobaoOrder]): """ 创建一个转运订单 :param create: List of taobao urls and details """ orders = gateway_order_list() ids = [get_url_param(i.GoodsLink, 'id') for o in orders for i in o.Items if 'id=' in i.GoodsLink] create = [o for o in create if o.date >= MIN_DATE and not any(get_url_param(i.url, 'id') in ids for i in o.items)] for c in create: shop_name = c.store.name shop_id = crawl(c.items[0].url)['data']['shop']['shopId'] goods = [] for i in c.items: id = get_url_param(i.url, 'id') cr = crawl(i.url) goods.append(CreateGood( desc=''.join(f'{k}:{v},'for k, v in i.sel) if 'sel' in i.__dict__ else i.name, goodsId=id, inventory=0, name=i.name, num=int(i.qty), shopName=shop_name, shopUrl='https:' + c.store.url, skus='0', totalPrice=float(i.price.split('¥')[-1]), url=i.url)) out = [CreateOrder('', shop_id, shop_name, goods)] print(f'Orders {c.id} created, sending...') j = json_stringify({'diy': out, 'transport': {'list': []}}) Path(f'crawler/order_{c.id}_created.json').write_text(j, 'utf-8') resp = r.post('https://front.superbuy.com/order/transport/create-diy-order', data=j.encode('utf-8'), headers={'content-type': 'application/json; charset=UTF-8'}).json() print(resp) def fill_express_no(taobao_data: list[TaobaoOrder]): orders = gateway_order_list(orderType=4) for o in orders: for i in o.Items: # 审核通过 means the delivery number hasn't been inputted yet if i.StatusName != '审核通过': continue # Find item in taobao tb = next(to for to in taobao_data for ti in to.items if get_url_param(ti.url, 'id') == i.goodsCode) # Check delivery status if tb.status != '卖家已发货' and tb.status != '快件已签收': continue # Find delivery company's id data = {'itemId': i.ItemId, 'warehouseId': i.WarehouseId, 'deliveryCompany': str(find_delivery_id(tb.delivery.expressName)), 'deliveryno': tb.delivery.expressId, 'platform': '3', 'itemStatus': '0', 'desc': ' '} # Send request print(f'Filling express no for {i.GoodsName}') # Mobile endpoint resp = r.post(f'https://api.superbuy.com/gateway/transport/fillExpressno/{user_id()}', json=data, headers=sign_api(dict(r.headers))) assert resp.json()['Code'] == 10000, resp.text + "\n" + str(data) print(resp.json()) def load_taobao(p: str = 'bought_items.json') -> list[TaobaoOrder]: p = Path(p) return jsn(p.read_text()) delivery_companies: list[SuperbuyDeliveryCompany] = jsn(Path('data/delivery.json').read_text('utf-8')) delivery_name_map = {d.name: d for d in delivery_companies} delivery_name_map.update({d.name.replace("快递", "物流"): d for d in delivery_companies}) delivery_name_map.update({d.name.replace("物流", "快递"): d for d in delivery_companies}) def find_delivery_id(name: str) -> int: return delivery_name_map[name].id if name in delivery_name_map else -1 class Handler(BaseHTTPRequestHandler): def do_POST(self): self.send_response(200) self.send_header("Content-type", "text/plain") self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() self.wfile.write("Up".encode()) print("Received!") body = self.rfile.read(int(self.headers.get('content-length', 0))).decode() if self.path.lower() == '/taobao': taobao_data: list[TaobaoOrder] = jsn(body) print(taobao_data) create_diy_order(taobao_data) fill_express_no(taobao_data) # Stop process sys.exit(0) # For desktop logins after captcha is added (not actually used) if self.path.lower() == '/login': # await fetch("http://127.0.0.1:12842/login", {method: "POST", body: document.cookie}) cookies = [[urllib.parse.unquote(k) for k in c.strip().split("=", 1)] for c in body.split(";")] # noinspection PyTypeChecker r.cookies.update(dict(cookies)) print(app_order_list()) def do_OPTIONS(self): self.send_response(200, "ok") self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() if __name__ == '__main__': par = argparse.ArgumentParser("Superbuy Order Helper") par.add_argument("-a", "--auth", help="Auth config path", default="auth.toml") args = par.parse_args() auth = toml.loads(Path(args.auth).read_text()) print(login(auth['login'], auth['passwd'])) # Start HTTP server asyncronously server = HTTPServer(("127.0.0.1", 12842), Handler) thread = threading.Thread(target=server.serve_forever) thread.start() # Open browser webbrowser.open_new_tab(f"https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm?script=12842")