Module gametime_watcher.cli

Command-line interface for the Gametime ticket watcher.

Functions

def build_parser() ‑> argparse.ArgumentParser
Expand source code
def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="gametime_watcher",
        description=(
            "List current Gametime ticket prices for an event and alert when "
            "seats in chosen sections drop below a per-ticket price.\n\n"
            "Use 'gametime_watcher search <query>' to find event links by "
            "team or performer name."
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("event", help="Gametime event/listing URL, short link, or 24-char event id")
    p.add_argument(
        "-s",
        "--sections",
        action="append",
        help="Section filter: ranges (200-299), exact (119), or group names "
        '("Solon Club"); comma-separated. May be given multiple times '
        "to combine filters. Default: all sections.",
    )
    p.add_argument(
        "-p",
        "--max-price",
        type=float,
        help="Maximum all-in price PER TICKET, in dollars.",
    )
    p.add_argument(
        "-q",
        "--quantity",
        type=int,
        default=1,
        help="Number of seats wanted together (default: 1). Only listings offering this lot size are kept.",
    )
    p.add_argument(
        "--allow-larger",
        action="store_true",
        help="Also keep listings that only offer a larger lot than --quantity.",
    )
    p.add_argument("--json", action="store_true", help="Output JSON instead of text.")
    p.add_argument(
        "--watch",
        action="store_true",
        help="Poll repeatedly and alert only on newly-seen matching listings.",
    )
    p.add_argument(
        "--interval",
        type=float,
        default=300.0,
        help="Polling interval in seconds when --watch is set (default: 300).",
    )
    p.add_argument("--webhook", help="POST a JSON payload to this URL on new matches.")
    p.add_argument(
        "--command",
        help="Run this command on new matches; the JSON payload is sent on stdin.",
    )
    p.add_argument("--user-agent", default=None, help="Override the HTTP User-Agent.")
    p.add_argument("--timeout", type=float, default=30.0, help="HTTP timeout in seconds.")
    return p
def main(argv: Optional[List[str]] = None) ‑> int
Expand source code
def main(argv: Optional[List[str]] = None) -> int:
    raw = argv if argv is not None else sys.argv[1:]
    # Dispatch to subcommands.
    if raw and raw[0] == "search":
        return _run_search(raw[1:])
    if raw and raw[0] == "scan-all":
        return _run_scan_all(raw[1:])

    parser = build_parser()
    args = parser.parse_args(argv)
    if args.user_agent is None:
        from .api import DEFAULT_USER_AGENT

        args.user_agent = DEFAULT_USER_AGENT
    if args.quantity is not None and args.quantity < 1:
        parser.error("--quantity must be >= 1")

    if not args.watch:
        try:
            event, all_listings, matches = _scan(args.event, args)
        except GametimeError as exc:
            print(f"error: {exc}", file=sys.stderr)
            return 2

        if args.json:
            print(_emit_json(event, matches))
        else:
            title = event.name if event and event.name else args.event
            when = f" @ {event.datetime_local}" if event and event.datetime_local else ""
            print(f"{title}{when}")
            print(f"Criteria: {_criteria_text(args)}")
            print(f"Found {len(matches)} of {len(all_listings)} listings:")
            for listing in matches:
                print("  " + _format_listing(listing))
        # Exit 0 when matches found, 1 when none (useful for cron/scripts).
        return 0 if matches else 1

    # --watch mode: poll and alert on new matches.
    seen: set = set()
    print(
        f"Watching {args.event} every {args.interval:g}s for {_criteria_text(args)} (Ctrl-C to stop)...",
        file=sys.stderr,
    )
    try:
        while True:
            try:
                event, all_listings, matches = _scan(args.event, args)
            except GametimeError as exc:
                print(f"[warn] scan failed: {exc}", file=sys.stderr)
            else:
                new = [m for m in matches if (m.id, m.price_total) not in seen]
                for m in matches:
                    seen.add((m.id, m.price_total))
                if new:
                    stamp = time.strftime("%Y-%m-%d %H:%M:%S")
                    print(f"[{stamp}] {len(new)} new matching listing(s):")
                    for listing in new:
                        print("  " + _format_listing(listing))
                    _notify(event, new, args)
            time.sleep(max(1.0, args.interval))
    except KeyboardInterrupt:
        print("\nStopped.", file=sys.stderr)
        return 0