diff --git a/assets/templates/ui/lobby/lobby_btn.png b/assets/templates/ui/lobby/lobby_btn.png index 70b4282..dfaa307 100644 Binary files a/assets/templates/ui/lobby/lobby_btn.png and b/assets/templates/ui/lobby/lobby_btn.png differ diff --git a/assets/templates/ui/lobby_btn.png b/assets/templates/ui/lobby_btn.png new file mode 100644 index 0000000..dfaa307 Binary files /dev/null and b/assets/templates/ui/lobby_btn.png differ diff --git a/scripts/make_traderie_csv.py b/scripts/make_traderie_csv.py new file mode 100644 index 0000000..7bdca9f --- /dev/null +++ b/scripts/make_traderie_csv.py @@ -0,0 +1,114 @@ +# Convert the stash sale list into the CSV that tools/traderie_lister.py expects. +# +# python scripts/make_traderie_csv.py # everything sellable +# python scripts/make_traderie_csv.py --runes # runes only +# +# Writes traderie_items.csv, then: +# python tools/traderie_lister.py --csv traderie_items.csv --dry-run +import csv +import os +import re +import sys + +_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +os.chdir(_root) + +SRC = "stash_sale_list.csv" +OUT = "traderie_items.csv" + +COLUMNS = ["name", "qty", "base_tier", "base_item", "ring_variant", + "amulet_variant", "ethereal", "pricing", "price_amount", + "price_currency"] + +# Runewords need a "Base Item" field. "No base" is the lister's own escape +# hatch for when the socketed base does not matter to the listing. +RUNEWORDS = {"spirit", "insight", "stealth", "lore", "smoke", "rhyme", + "ancients pledge", "leaf", "malice", "steel", "zephyr", "king's grace", + "edge", "harmony", "white", "peace", "myth", "black", "honor"} + +# Not tradeable / not worth a listing. Potions are consumables; sub-perfect gems +# were already dropped upstream but are re-checked here so this script is safe to +# run against any sale list. +SKIP_WORDS = ("rejuvenation potion", "scroll of", "tome of", "key of", + "chipped", "flawed", "flawless") + + +def title_case(name: str) -> str: + """Stash tooltips are ALL CAPS; Traderie searches title case. + + Apostrophes matter: "TAL RASHA'S" must become "Tal Rasha's", not + "Tal Rasha'S", or the dropdown search misses. + """ + out = [] + for word in name.lower().split(): + if "'" in word: + head, _, tail = word.partition("'") + out.append(head.capitalize() + "'" + tail) + else: + out.append(word.capitalize()) + return " ".join(out) + + +def main(): + runes_only = "--runes" in sys.argv + with open(SRC, encoding="utf-8") as f: + rows = list(csv.DictReader(f)) + + seen: dict[str, dict] = {} + skipped = 0 + for r in rows: + raw = (r["name"] or "").strip() + low = raw.lower() + if not raw or any(w in low for w in SKIP_WORDS): + skipped += 1 + continue + if runes_only and not low.endswith("rune"): + continue + + name = title_case(raw) + qty = r.get("qty") or "1" + try: + qty = int(qty) + except ValueError: + qty = 1 + + # The same item can sit in several cells (2x TARNHELM, 4x SMALL CHARM OF + # INERTIA). Traderie wants one listing with a quantity, not N listings. + if name in seen: + seen[name]["qty"] += qty + continue + + base_item = "No base" if name.lower() in RUNEWORDS else "" + seen[name] = { + "name": name, + "qty": qty, + "base_tier": "", + "base_item": base_item, + "ring_variant": "", + "amulet_variant": "", + "ethereal": "", + # You said you would set prices yourself — ask_for_offers means the + # listing goes up without committing to a number. + "pricing": "ask_for_offers", + "price_amount": "", + "price_currency": "", + } + + out = sorted(seen.values(), key=lambda r: (not r["name"].endswith("Rune"), r["name"])) + with open(OUT, "w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=COLUMNS) + w.writeheader() + w.writerows(out) + + print(f"wrote {OUT}: {len(out)} listings ({skipped} skipped as untradeable)") + runes = [r for r in out if r["name"].endswith("Rune")] + print(f" runes: {len(runes)} other: {len(out) - len(runes)}") + needs_base = [r["name"] for r in out if r["base_item"]] + if needs_base: + print(f" runewords given base_item='No base': {', '.join(needs_base)}") + print("\nnext:") + print(f" python tools/traderie_lister.py --csv {OUT} --dry-run") + + +if __name__ == "__main__": + main() diff --git a/tools/ChatGPT Installer.exe b/tools/ChatGPT Installer.exe new file mode 100644 index 0000000..20ce34d Binary files /dev/null and b/tools/ChatGPT Installer.exe differ diff --git a/tools/Microsoft.Services.Store.winmd b/tools/Microsoft.Services.Store.winmd new file mode 100644 index 0000000..e9b62b4 Binary files /dev/null and b/tools/Microsoft.Services.Store.winmd differ diff --git a/tools/traderie_lister.py b/tools/traderie_lister.py new file mode 100644 index 0000000..753fbb8 --- /dev/null +++ b/tools/traderie_lister.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +""" +traderie_lister.py + +Automates creating "sell" listings on Traderie (https://traderie.com) for +Diablo II: Resurrected, based on the workflow used to bulk-list ~20 items +for account "alexpolo" in September 2026. + +HOW IT WORKS +------------ +Traderie's "Add Listing" form is a React app built on react-select style +dropdowns. It does NOT have simple