115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
# 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()
|