diff --git a/fifa17-recon/tools/fut_store.py b/fifa17-recon/tools/fut_store.py index 3946a07..63a6e2b 100644 --- a/fifa17-recon/tools/fut_store.py +++ b/fifa17-recon/tools/fut_store.py @@ -165,6 +165,28 @@ class Store: out["players"] = players return out + # ---- transfer-market listings (user's own sale pile) ------------------- + def list_for_sale(self, item_id, start, buynow): + with _LOCK: + p = self.load() + p.setdefault("listings", []) + p["listings"] = [l for l in p["listings"] if l.get("itemId") != item_id] + tid = 900500000 + p.get("nextListingSeq", 0) + p["nextListingSeq"] = p.get("nextListingSeq", 0) + 1 + p["listings"].append({"tradeId": tid, "itemId": item_id, + "startingBid": start, "buyNowPrice": buynow}) + self._save() + return tid + + def listings(self): + return self.load().get("listings", []) + + def remove_listing(self, tid): + with _LOCK: + p = self.load() + p["listings"] = [l for l in p.get("listings", []) if l.get("tradeId") != tid] + self._save() + def new_item_id(self): with _LOCK: p = self.load(); i = p["nextItemId"]; p["nextItemId"] += 1; self._save() diff --git a/fifa17-recon/tools/test_market_buy.py b/fifa17-recon/tools/test_market_buy.py index 2acd5cc..37d34f9 100644 --- a/fifa17-recon/tools/test_market_buy.py +++ b/fifa17-recon/tools/test_market_buy.py @@ -62,8 +62,26 @@ def main(): ok("insufficient funds -> 461", c2 == 461, str(c2)) ok("coins unchanged on failed buy", u.STORE.coins() == 10, str(u.STORE.coins())) + # ---- SELL / list flow ---- + owned_id = u.STORE.items()[0]["id"] + _, s = u.auctionhouse_route(FakeH("POST", "/ut/game/fifa17/auctionhouse", + json.dumps({"itemData": {"id": owned_id}, "startingBid": 1000, "buyNowPrice": 5000}).encode())) + ltid = s.get("id") + ok("list returns a tradeId", isinstance(ltid, int), repr(s)) + _, tp = u.tradepile_route(FakeH("GET", "/ut/game/fifa17/tradePile")) + ok("tradePile shows the listing", tp.get("total") == 1, repr(tp.get("total"))) + if tp.get("auctionInfo"): + rec = tp["auctionInfo"][0] + ok("listing tradeId matches", rec.get("tradeId") == ltid) + ok("listing buyNowPrice preserved", rec.get("buyNowPrice") == 5000) + ok("listing itemData is object", isinstance(rec.get("itemData"), dict)) + ok("listing itemData.attributeList is array", isinstance(rec.get("itemData", {}).get("attributeList"), list)) + u.delete_trade_route(FakeH("DELETE", f"/ut/delete/game/fifa17/trade/{ltid}")) + _, tp2 = u.tradepile_route(FakeH("GET", "/ut/game/fifa17/tradePile")) + ok("delist empties tradePile", tp2.get("total") == 0, repr(tp2.get("total"))) + os.remove(os.environ["FUT_PROFILE"]) - print(f"{'PASS' if not fails else 'FAIL'} - market buy flow " + print(f"{'PASS' if not fails else 'FAIL'} - market buy+sell flow " f"({0 if fails else 'all'} checks; {len(fails)} failed)") for f in fails: print(" FAIL:", f) diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index f2c9576..f388d2a 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -190,7 +190,7 @@ ROUTES = [ (re.compile(G + r"/watchList"), lambda m, h: watchlist_route(h)), (re.compile(G + r"/auctionhouse"), lambda m, h: auctionhouse_route(h)), (re.compile(G + r"/marketdata"), lambda m, h: (200, {"minPrice": 150, "maxPrice": 15000})), - (re.compile(r"/ut/delete/game/[^/]+/trade"), lambda m, h: (200, {})), + (re.compile(r"/ut/delete/game/[^/]+/trade"), lambda m, h: delete_trade_route(h)), (re.compile(r"/ut/delete/game/[^/]+/watchList"), lambda m, h: (200, {})), (re.compile(G + r"/club"), lambda m, h: (200, {"itemData": STORE.items()})), ] @@ -383,6 +383,16 @@ def auctionhouse_route(h): # POST = FutISStart (list item for sale) -> {"id": new tradeId} # PUT = .../relist (relist all expired) -> ack {} if h.command == "POST": + # FutISStart: list an owned club item for sale -> {"id": tradeId} + try: + b = json.loads(h._body.decode()) if getattr(h, "_body", b"") else {} + except Exception: + b = {} + item_id = (b.get("itemData") or {}).get("id") or b.get("itemId") + if item_id: + tid = STORE.list_for_sale(item_id, b.get("startingBid", 150), b.get("buyNowPrice", 0)) + log(" MARKET: listed item %s -> tradeId %d" % (item_id, tid)) + return 200, {"id": tid} return 200, {"id": STORE.new_item_id()} if h.command == "PUT": return 200, {} @@ -427,8 +437,31 @@ def trade_route(h): def tradepile_route(h): - # The user's OWN sale pile -- empty until they list something (no live sell flow yet). - return 200, {"auctionInfo": [], "credits": STORE.coins(), "total": 0} + # The user's OWN sale pile: build a validated auction record per active listing + # from the owned club item + its list prices. Freeze-safe (same record shape). + by_id = {it["id"]: it for it in STORE.items()} + seller = STORE.profile().get("personaName", "OpenFUT") + recs = [] + for l in STORE.listings(): + it = by_id.get(l["itemId"]) + if not it: + continue + card = dict(it); card["itemState"] = "listFS" + recs.append({ + "tradeId": l["tradeId"], "itemData": card, "tradeState": "active", + "buyNowPrice": l.get("buyNowPrice", 0), "startingBid": l.get("startingBid", 150), + "currentBid": 0, "bidState": "none", "expires": 3600, + "sellerName": seller, "sellerEstablished": 1, "watched": False, "coinsProcessed": 0, + }) + return 200, {"auctionInfo": recs, "credits": STORE.coins(), "total": len(recs)} + + +def delete_trade_route(h): + # DELETE ut/delete/game/fifa17/trade/{id} -- remove a listing from the sale pile. + m = re.search(r"/trade/(\d+)", h.path) + if m: + STORE.remove_listing(int(m.group(1))) + return 200, {} def watchlist_route(h):