From 88dc651232743fd8b6be77c18cdc76716fe3ebd4 Mon Sep 17 00:00:00 2001 From: shoko <270575765+shokollm@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:26:29 +0000 Subject: [PATCH] polymarket-browse: add --search, --telegram, --matches-only, --non-matches-only flags; fix partial fetch warnings; clean up output formatting --- skills/polymarket-browse/SKILL.md | 90 ++--- skills/polymarket-browse/scripts/browse.py | 401 +++++++++++++++++---- 2 files changed, 369 insertions(+), 122 deletions(-) diff --git a/skills/polymarket-browse/SKILL.md b/skills/polymarket-browse/SKILL.md index f37d371..29e7078 100644 --- a/skills/polymarket-browse/SKILL.md +++ b/skills/polymarket-browse/SKILL.md @@ -12,73 +12,74 @@ Browse tradeable Polymarket prediction market events by game category. **For Hermes Agent users:** ```bash -hermes skills install https://git.fbrns.co/shoko/jujutsu-skills#polymarket-browse +hermes skills install https://github.com/shokollm/jujutsu-skills#polymarket-browse ``` **For OpenClaw users:** ```bash # Clone the repo -git clone https://git.fbrns.co/shoko/jujutsu-skills.git ~/jujutsu-skills +git clone https://github.com/shokollm/jujutsu-skills.git ~/jujutsu-skills # Copy skill to your OpenClaw skills folder cp -r ~/jujutsu-skills/skills/polymarket-browse ~/.openclaw/skills/ ``` -**Manual installation:** +**Optional: For better experience, install the Polymarket MCP server:** +This lets the agent answer questions about Polymarket markets, rules, and trading mechanics. ```bash -# Clone the repo -git clone https://git.fbrns.co/shoko/jujutsu-skills.git ~/jujutsu-skills - -# Copy skill to your Hermes skills folder -cp -r ~/jujutsu-skills/skills/polymarket-browse ~/.hermes/skills/ +# Ask your agent to install it for you, or add to config.yaml: +hermes mcp add polymarket https://docs.polymarket.com/mcp ``` ## Usage ``` -polymarket-browse [--category "Counter Strike"] [--limit 5] [--detail N] +polymarket-browse [--category "Counter Strike"] [--limit 5] [--matches N] [--non-matches N] [--search "TeamName"] [--matches-only] [--non-matches-only] [--detail N] [--raw] [--telegram] ``` ## Arguments - `--category` : Game category to browse. Options: All Esports, Counter Strike, League of Legends, Dota 2, Valorant, NBA, NFL, UFC, Tennis (default: Counter Strike) -- `--limit` : Max number of events to show (default: 5) -- `--detail` : Index of event (1-indexed) to show detailed markets for. Defaults to 1 (first event). Set to 0 to disable. +- `--limit` : Max events per section (match + non-match). Default: 5 +- `--matches` : Max match markets to show. Default: --limit +- `--non-matches` : Max non-match markets to show. Default: --limit +- `--search` : Free-text team/term search within the selected category. Appends to the category query. Example: `--category "Counter Strike" --search "FlyQuest"` +- `--matches-only` : Show only match markets (suppress non-match section). +- `--non-matches-only` : Show only non-match markets (suppress match section). +- `--detail` : Index of match event (1-indexed) to show detailed markets. Default: 1. Set to 0 to disable. - `--list-categories` : List available game categories and exit +- `--raw` : Show all events without tradeable filter (for debugging). Includes fetch stats. +- `--telegram` : Send results to Telegram. Requires `BOT_TOKEN` and `CHAT_ID` in environment variables. ## Output Format -Each event displays in a **6-line format**: +Output is split into two sections: ``` -=== DOTA 2 === -Query: 'Dota2' | Total API: 54 | Tradeable: 3 -Current time (WIB): 13:58 WIB | Mar 24, 2026 +=== COUNTER STRIKE === +Current time (WIB): 17:00 WIB | Mar 24, 2026 -1. [Dota 2: Yakult Brothers vs BetBoom Team (BO2)](https://polymarket.com/market/...) - Mar 24, 19:00 WIB | In 5h +MATCH MARKETS - Vol: $4,952 - Tournament: ESL One Birmingham Group A - Odds: Yakult Brothers 29c | 71c BetBoom Team + 1. [Counter-Strike: TheMongolz vs Spirit (BO3)](https://polymarket.com/market/...) + Mar 24, 03:45 WIB | LIVE + Vol: $2,626,029 + Tournament: BLAST Open Rotterdam Group B + Odds: TheMongolz 100c | 0c Spirit -2. [Dota 2: GamerLegion vs Team Yandex (BO2)](https://polymarket.com/market/...) - Mar 24, 19:00 WIB | In 5h +NON-MATCH MARKETS - Vol: $3,944 - Tournament: ESL One Birmingham Group A - Odds: GamerLegion 28c | 72c Team Yandex + 1. [Blast Open Rotterdam 2026: Winner](https://polymarket.com/event/...) + Feb 28, 04:43 WIB | 24d ago + Markets: 17 | Total Vol: $958,116 ``` -Format per event: -- **Line 1**: `[number]. [Title](url)` — title without tournament name -- **Line 2**: `Date, Time WIB | Relative` (e.g., "Mar 24, 19:00 WIB | In 5h") -- **Line 3**: (empty separator) -- **Line 4**: `Vol: $XXX` -- **Line 5**: `Tournament: [tournament name]` -- **Line 6**: `Odds: [Team A] [Odds A] | [Odds B] [Team B]` +**Match Markets** are actual head-to-head matches with moneyline odds (sorted by volume). +**Non-match Markets** are tournament futures, props, and other markets without direct match odds. -Time shows in WIB (UTC+7 for Indonesian users). Relative time shows "In Xh", "Xd ago", or "LIVE". +Stats line (Fetched / Total / Match counts) only shown when `--raw` flag is used. + +If a fetch is interrupted (API error/timeout), a `WARNING: Partial fetch` line appears showing data may be incomplete. ## Game Categories @@ -96,7 +97,13 @@ Time shows in WIB (UTC+7 for Indonesian users). Relative time shows "In Xh", "Xd ## Filters Applied -Events are filtered to show only **tradeable** matches: +The script classifies every event into one of two categories: + +**Match Markets**: Events that are actual head-to-head matches (have `seriesSlug` + `gameId`, OR title contains " vs "). + +**Non-match Markets**: Everything else — tournament futures, prop bets, player props, etc. + +Tradeable match markets additionally require: - Must have seriesSlug + gameId (actual match, not tournament future) - ML volume > 0 - acceptingOrders = true @@ -105,22 +112,19 @@ Events are filtered to show only **tradeable** matches: - ML bestAsk > 0.01 (market hasn't converged toward underdog) - BO2 matches that ended in a tie (1-1) are filtered out - Event endDate has not passed (API doesn't always close ML market promptly) +- Match startTime is not more than 4 hours in the past (matches that already ended are filtered out) -Markets are filtered to only tradeable ones: -- acceptingOrders = true -- bestBid < 0.99 -- bestAsk > 0.01 -- closed = false +Use `--raw` to disable the tradeable filter and see all match markets regardless of volume or open orders. + +## Pagination + +The script fetches **ALL pages** until the API runs out of results (up to 100 pages as a safety cap). ## Rate Limiting - Exponential backoff: 2s → 4s → 8s → 16s → 32s - Max 5 retries before aborting -## Known Issues - -- **BO2 matches that end in a tie (1-1)**: Filtered out by checking if all child_moneyline markets are closed. - ## Odds Format All odds are shown in **cents** format: diff --git a/skills/polymarket-browse/scripts/browse.py b/skills/polymarket-browse/scripts/browse.py index 03de0ce..49473a8 100644 --- a/skills/polymarket-browse/scripts/browse.py +++ b/skills/polymarket-browse/scripts/browse.py @@ -14,9 +14,7 @@ from datetime import datetime, timezone, timedelta # CONFIG # ============================================================ -FETCH_PAGES = 4 -PAGE_SIZE = 5 -DISPLAY_MAX = 10 +PAGE_SIZE = 50 MAX_RETRIES = 5 INITIAL_RETRY_DELAY = 2 # exponential backoff starts at 2s @@ -66,20 +64,29 @@ def fetch_page(q, page=1, max_retries=MAX_RETRIES, initial_delay=INITIAL_RETRY_D return None return None -def fetch_all_pages(q, num_pages=FETCH_PAGES): +def fetch_all_pages(q, max_pages=100): + """ + Fetch ALL pages until pagination ends. + max_pages is a safety cap to prevent infinite loops. + """ all_events = [] total_raw = 0 - for page in range(1, num_pages + 1): - time.sleep(1) + for page in range(1, max_pages + 1): + time.sleep(0.2) # small delay between pages (API rate limit is generous) data = fetch_page(q, page) if data is None: break events = data.get("events", []) - total_raw = data.get("pagination", {}).get("totalResults", "?") + total_raw = data.get("pagination", {}).get("totalResults", 0) all_events.extend(events) - if len(events) < PAGE_SIZE: + # Stop when we get 0 events (no more pages), + # OR when we've fetched >= total results + if len(events) == 0: break - return {"events": all_events, "total_raw": total_raw} + if len(all_events) >= total_raw: + break + partial = (total_raw > 0 and len(all_events) < total_raw) + return {"events": all_events, "total_raw": total_raw, "partial": partial} # ============================================================ # FILTERS @@ -88,6 +95,16 @@ def fetch_all_pages(q, num_pages=FETCH_PAGES): def is_match_market(e): return (e.get("seriesSlug") and e.get("gameId")) or " vs " in e.get("title", "") +def get_event_url(e): + """Return the correct Polymarket URL for an event. + Match markets use /market/, non-match events use /event/. + """ + slug = e.get("slug", "") + if is_match_market(e): + return f"https://polymarket.com/market/{slug}" + else: + return f"https://polymarket.com/event/{slug}" + def get_ml_market(e): for m in e.get("markets", []): if m.get("sportsMarketType") == "moneyline": @@ -152,6 +169,20 @@ def is_tradeable_event(e): except: pass + # Filter: match has already started (startTime is in the past) + start_str = e.get("startTime") or e.get("startDate", "") + if start_str: + try: + start_dt = datetime.fromisoformat(start_str.replace('Z', '+00:00')) + now = datetime.now(timezone.utc) + if start_dt < now: + # Check if it's recently started (within 4h) — consider those "live" still + hours_ago = (now - start_dt).total_seconds() / 3600 + if hours_ago > 4: + return False + except: + pass + return True def is_tradeable_market(m): @@ -273,8 +304,22 @@ def get_match_time_str(e): except: return "" -def filter_events(events): - return [e for e in events if is_match_market(e) and is_tradeable_event(e)] +def filter_events(events, tradeable_only=True): + """ + Classify events into match_markets and non_match_markets. + If tradeable_only=True, also filter out non-tradeable events. + """ + match_events = [] + non_match_events = [] + + for e in events: + if is_match_market(e): + if not tradeable_only or is_tradeable_event(e): + match_events.append(e) + else: + non_match_events.append(e) + + return match_events, non_match_events def sort_events(events): return sorted(events, key=get_ml_volume, reverse=True) @@ -283,16 +328,20 @@ def sort_events(events): # BROWSE # ============================================================ -def browse_events(q, display_max=DISPLAY_MAX): - result = fetch_all_pages(q, FETCH_PAGES) +def browse_events(q, matches_max=10, non_matches_max=10, tradeable_only=True): + result = fetch_all_pages(q) events = result["events"] - filtered = filter_events(events) - sorted_events = sort_events(filtered) + match_events, non_match_events = filter_events(events, tradeable_only) + sorted_match = sort_events(match_events) return { "query": q, "total_raw": result["total_raw"], - "total_filtered": len(filtered), - "events": sorted_events[:display_max], + "total_fetched": len(events), + "total_match": len(match_events), + "total_non_match": len(non_match_events), + "match_events": sorted_match[:matches_max], + "non_match_events": non_match_events[:non_matches_max], + "partial": result.get("partial", False), } # ============================================================ @@ -312,7 +361,7 @@ def format_event(e): "title": e.get("title", ""), "time_status": time_status, "time_urgency": urgency, - "url": f"https://polymarket.com/market/{e.get('slug')}", + "url": get_event_url(e), "livestream": e.get("resolutionSource"), "outcomes": outcomes, "prices": prices, @@ -335,7 +384,7 @@ def format_detail_event(e): return { "title": e.get("title", ""), "time_status": time_status, - "url": f"https://polymarket.com/market/{e.get('slug')}", + "url": get_event_url(e), "livestream": e.get("resolutionSource"), "outcomes": json.loads(ml.get("outcomes", "[]")) if ml else [], "prices": json.loads(ml.get("outcomePrices", "[]")) if ml else [], @@ -389,8 +438,8 @@ def get_start_time_wib(e): else: hours_until = delta.total_seconds() / 3600 if hours_until < 1: - mins = int(delta.total_seconds() / 60) - rel_str = f"In {mins}m" + mins_until = int(delta.total_seconds() / 60) + rel_str = f"In {mins_until}m" elif hours_until < 24: rel_str = f"In {int(hours_until)}h" else: @@ -416,62 +465,82 @@ def get_tournament(title): return " - ".join(parts[1:]).strip() return "" -def print_browse(events, category, total_raw, total_filtered): +def print_browse(match_events, non_match_events, category, total_raw, total_fetched, total_match, total_non_match, raw_mode=False, partial=False, non_matches_max=5, matches_only=False, non_matches_only=False): from datetime import datetime, timezone, timedelta now_utc = datetime.now(timezone.utc) utc7 = timezone(timedelta(hours=7)) now_utc7 = now_utc.astimezone(utc7) header_date = get_header_date() - print(f"\n{'='*60}") - print(f"=== {category.upper()} ===") - print(f"{'='*60}") - print(f"Query: '{GAME_CATEGORIES[category]}' | Total API: {total_raw} | Tradeable: {total_filtered}") + print(f"\n=== {category.upper()}{' [RAW]' if raw_mode else ''} ===") print(f"Current time (WIB): {now_utc7.strftime('%H:%M WIB')} | {header_date}") - if not events: - print(" No tradeable events found.") - return + if raw_mode: + print(f"Fetched: {total_fetched} / Total API: {total_raw} | Match: {total_match} | Non-match: {total_non_match}") + if partial: + print(f"WARNING: Partial fetch (API error or timeout) — data may be incomplete") - for i, e in enumerate(events, 1): - f = format_event(e) - ml = get_ml_market(e) - outcomes = json.loads(ml.get("outcomes", "[]")) if ml else [] - prices = json.loads(ml.get("outcomePrices", "[]")) if ml else [] - vol = f["volume"] - title = f["title"] - url = f["url"] - start_time_wib, rel_time = get_start_time_wib(e) - - team_a = outcomes[0] if len(outcomes) > 0 else "?" - team_b = outcomes[1] if len(outcomes) > 1 else "?" - odds_a = format_odds(float(prices[0])) if len(prices) > 0 else "?" - odds_b = format_odds(float(prices[1])) if len(prices) > 1 else "?" - - # Extract title without tournament for line 1 - # Title format: "Category: TeamA vs TeamB (BO/X) - Tournament" - # We want: "Category: TeamA vs TeamB (BO/X)" - if " - " in title: - title_clean = title.split(" - ")[0].strip() + # --- MATCH MARKETS --- + if not matches_only and not non_matches_only: + # Default: show both + show_matches = True + show_non_matches = True + elif matches_only: + show_matches = True + show_non_matches = False + else: + show_matches = False + show_non_matches = True + + if show_matches: + print(f"\nMATCH MARKETS") + if not match_events: + print(" No match markets found.") else: - title_clean = title + for i, e in enumerate(match_events, 1): + f = format_event(e) + ml = get_ml_market(e) + outcomes = json.loads(ml.get("outcomes", "[]")) if ml else [] + prices = json.loads(ml.get("outcomePrices", "[]")) if ml else [] + vol = f["volume"] + title = f["title"] + url = f["url"] + start_time_wib, rel_time = get_start_time_wib(e) + + team_a = outcomes[0] if len(outcomes) > 0 else "?" + team_b = outcomes[1] if len(outcomes) > 1 else "?" + odds_a = format_odds(float(prices[0])) if len(prices) > 0 else "?" + odds_b = format_odds(float(prices[1])) if len(prices) > 1 else "?" + + if " - " in title: + title_clean = title.split(" - ")[0].strip() + else: + title_clean = title + + tournament = get_tournament(title) + + print(f"\n {i}. [{title_clean}]({url})") + print(f" {start_time_wib} | {rel_time}") + print(f" Vol: ${vol:,.0f}") + if tournament: + print(f" Tournament: {tournament}") + print(f" Odds: {team_a} {odds_a} | {odds_b} {team_b}") + + # --- NON-MATCH MARKETS --- + if show_non_matches and non_match_events: + print(f"\nNON-MATCH MARKETS") - tournament = get_tournament(title) - - # New format: - # 1. [Title](url) <- title without tournament - # 2. Date, Time WIB | Relative - # (empty) - # Vol: $XXX - # Tournament: XXXXX - # Odds: TeamA Xc | Xc TeamB - print(f"\n {i}. [{title_clean}]({url})") - print(f" {start_time_wib} | {rel_time}") - print(f"") - print(f" Vol: ${vol:,.0f}") - if tournament: - print(f" Tournament: {tournament}") - print(f" Odds: {team_a} {odds_a} | {odds_b} {team_b}") + for i, e in enumerate(non_match_events[:non_matches_max], 1): + title = e.get("title", "?") + url = get_event_url(e) + start_time_wib, rel_time = get_start_time_wib(e) + + total_vol = sum(float(m.get("volume", 0)) for m in e.get("markets", [])) + market_count = len(e.get("markets", [])) + + print(f"\n {i}. [{title}]({url})") + print(f" {start_time_wib} | {rel_time}") + print(f" Markets: {market_count} | Total Vol: ${total_vol:,.0f}") def print_detail(e, detail): from datetime import datetime, timezone, timedelta @@ -479,9 +548,7 @@ def print_detail(e, detail): utc7 = timezone(timedelta(hours=7)) now_utc7 = now_utc.astimezone(utc7) - print(f"\n{'='*60}") - print(f"=== DETAIL: {detail['title'][:60]} ===") - print(f"{'='*60}") + print(f"\n{detail['title']}") print(f"URL: {detail['url']}") print(f"Livestream: {detail['livestream']}") @@ -499,6 +566,143 @@ def print_detail(e, detail): print(f" Vol: ${m['volume']:,.0f} | {spread_str}") print(f" URL: {m['url']}") +# ============================================================ +# TELEGRAM +# ============================================================ + +def send_to_telegram(match_events, non_match_events, category, matches_only=False, non_matches_only=False): + """Send browse results to Telegram. Reads BOT_TOKEN and CHAT_ID from environment.""" + import os + bot_token = os.environ.get("BOT_TOKEN") + chat_id = os.environ.get("CHAT_ID") + if not bot_token or not chat_id: + print("WARNING: BOT_TOKEN or CHAT_ID not set in environment. Skipping Telegram send.") + return + + from datetime import datetime, timezone, timedelta + now_utc = datetime.now(timezone.utc) + utc7 = timezone(timedelta(hours=7)) + now_utc7 = now_utc.astimezone(utc7) + header_date = now_utc7.strftime("%b %d, %Y") + + # Determine sections to show + show_matches = (not matches_only and not non_matches_only) or matches_only + show_non_matches = (not matches_only and not non_matches_only) or non_matches_only + + def send(text): + result = subprocess.run( + ["curl", "-s", f"https://api.telegram.org/bot{bot_token}/sendMessage", + "-d", f"chat_id={chat_id}", + "-d", f"text={text}", + "-d", "parse_mode=HTML", + "-d", "disable_web_page_preview=true"], + capture_output=True + ) + resp = json.loads(result.stdout.decode()) + if resp.get("ok"): + print(f" Sent msg {resp['result']['message_id']}") + else: + print(f" Error: {resp.get('description')}") + + # Build sections + lines = [f"{category.upper()} | {header_date}"] + lines.append("") + + if show_matches: + lines.append("MATCH MARKETS") + lines.append("") + if not match_events: + lines.append(" No match markets found.") + else: + for i, e in enumerate(match_events, 1): + ml = get_ml_market(e) + outcomes = json.loads(ml.get("outcomes", "[]")) if ml else [] + prices = json.loads(ml.get("outcomePrices", "[]")) if ml else [] + vol = get_ml_volume(e) + title = e.get("title", "?") + url = get_event_url(e) + start_time_wib, rel_time = get_start_time_wib(e) + team_a = outcomes[0] if len(outcomes) > 0 else "?" + team_b = outcomes[1] if len(outcomes) > 1 else "?" + odds_a = format_odds(float(prices[0])) if len(prices) > 0 else "?" + odds_b = format_odds(float(prices[1])) if len(prices) > 1 else "?" + tournament = get_tournament(title) + title_clean = title.split(" - ")[0].strip() if " - " in title else title + lines.append(f"{i}. {title_clean}") + lines.append(f" {start_time_wib} | {rel_time}") + lines.append(f" Vol: ${vol:,.0f}") + if tournament: + lines.append(f" Tournament: {tournament}") + lines.append(f" Odds: {team_a} {odds_a} | {odds_b} {team_b}") + lines.append("") + lines.append("") + + if show_non_matches: + lines.append("NON-MATCH MARKETS") + lines.append("") + if not non_match_events: + lines.append(" No non-match markets found.") + else: + for i, e in enumerate(non_match_events, 1): + title = e.get("title", "?") + url = get_event_url(e) + start_time_wib, rel_time = get_start_time_wib(e) + total_vol = sum(float(m.get("volume", 0)) for m in e.get("markets", [])) + market_count = len(e.get("markets", [])) + lines.append(f"{i}. {title}") + lines.append(f" {start_time_wib} | {rel_time}") + lines.append(f" Markets: {market_count} | Total Vol: ${total_vol:,.0f}") + lines.append("") + + # Chunk by 10 items (events), respecting 4096 char Telegram limit + text = "\n".join(lines) + if len(text) <= 4096: + send(text) + return + + # Split into chunks of 10 events + all_items = [] + in_match = True + for line in lines: + if line == "MATCH MARKETS": + in_match = True + elif line == "NON-MATCH MARKETS": + in_match = False + elif line.startswith("") and ". " in line and "" in line: + all_items.append((in_match, line)) + + chunk = [] + chunk_len = 0 + chunk_num = 1 + + # Header is always first + header = f"{category.upper()} | {header_date}\n" + if show_matches: + header += "\nMATCH MARKETS\n\n" + if show_non_matches: + header += "\nNON-MATCH MARKETS\n\n" + + for is_match, item_line in all_items: + test_chunk = chunk + [item_line, ""] + test_text = header + "\n".join(chunk) + "\n".join(test_chunk) + if len(test_text) > 4096 or len(chunk) >= 10: + # Send current chunk + msg = header + "\n".join(chunk) + send(msg) + chunk = [item_line, ""] + header = f"{category.upper()} (cont.) | {header_date}\n" + if show_matches and is_match: + header += "\nMATCH MARKETS\n\n" + elif show_non_matches and not is_match: + header += "\nNON-MATCH MARKETS\n\n" + else: + chunk.extend([item_line, ""]) + + if chunk: + msg = header + "\n".join(chunk) + send(msg) + + # ============================================================ # MAIN # ============================================================ @@ -509,11 +713,25 @@ def main(): choices=list(GAME_CATEGORIES.keys()), help="Game category to browse") parser.add_argument("--limit", type=int, default=5, - help="Max events to show") + help="Max events per section (match + non-match). Default: 5") + parser.add_argument("--matches", type=int, default=None, + help="Max match markets to show. Default: --limit") + parser.add_argument("--non-matches", type=int, default=None, + help="Max non-match markets to show. Default: --limit") + parser.add_argument("--search", type=str, default=None, + help="Free-text team/term search within the selected category. Overrides default query.") + parser.add_argument("--matches-only", action="store_true", + help="Show only match markets (suppress non-match section).") + parser.add_argument("--non-matches-only", action="store_true", + help="Show only non-match markets (suppress match section).") parser.add_argument("--list-categories", action="store_true", help="List available game categories and exit") parser.add_argument("--detail", type=int, default=1, - help="Index of event (1-indexed) to show detailed markets for. Default: 1. Set to 0 to disable.") + help="Index of match event (1-indexed) to show detailed markets. Default: 1. Set to 0 to disable.") + parser.add_argument("--raw", action="store_true", + help="Show all events without tradeable filter (for debugging).") + parser.add_argument("--telegram", action="store_true", + help="Send results to Telegram (BOT_TOKEN and CHAT_ID must be set in environment).") args = parser.parse_args() if args.list_categories: @@ -522,28 +740,53 @@ def main(): print(f" - {name}") return - search_term = GAME_CATEGORIES[args.category] + category_term = GAME_CATEGORIES[args.category] + search_term = f"{category_term} {args.search}" if args.search else category_term + tradeable_only = not args.raw + matches_max = args.matches if args.matches is not None else args.limit + non_matches_max = args.non_matches if args.non_matches is not None else args.limit - print(f"\nFetching {args.category} events...") + if args.search: + print(f"\nFetching {args.category} events matching '{args.search}'...") + else: + print(f"\nFetching {args.category} events...") - result = browse_events(search_term, display_max=args.limit) + result = browse_events(search_term, matches_max=matches_max, non_matches_max=non_matches_max, tradeable_only=tradeable_only) print_browse( - result["events"], + result["match_events"], + result["non_match_events"], args.category, result["total_raw"], - result["total_filtered"] + result["total_fetched"], + result["total_match"], + result["total_non_match"], + raw_mode=args.raw, + partial=result.get("partial", False), + non_matches_max=non_matches_max, + matches_only=args.matches_only, + non_matches_only=args.non_matches_only ) # Print detail for selected event if any - if result["events"] and args.detail > 0: + if result["match_events"] and args.detail > 0: print("\n") idx = args.detail - 1 - if idx < 0 or idx >= len(result["events"]): + if idx < 0 or idx >= len(result["match_events"]): idx = 0 - detail_event = result["events"][idx] + detail_event = result["match_events"][idx] detail = format_detail_event(detail_event) print_detail(detail_event, detail) + + # Send to Telegram if requested + if args.telegram: + send_to_telegram( + result["match_events"], + result["non_match_events"], + args.category, + matches_only=args.matches_only, + non_matches_only=args.non_matches_only + ) if __name__ == "__main__": main()