#!/usr/bin/env python3 """ slopbot - Zulip bot that replies with stock prices when it sees $TICKER patterns. Usage: python main.py Requires a .zuliprc file in the same directory (see .zuliprc.example). """ import re import zulip import yfinance as yf # Regex to match stock tickers like $NVDA, $AAPL, $BRK TICKER_RE = re.compile(r'\$([A-Z]{1,5})') def fetch_price(ticker: str) -> str: """Return a formatted price string for a ticker, or an error message.""" try: info = yf.Ticker(ticker).fast_info price = info["lastPrice"] return f"${ticker}: ${price:,.2f}" except Exception as e: return f"${ticker}: couldn't fetch price ({e})" def handle_event(event: dict, client: zulip.Client) -> None: """Process a Zulip event and reply if tickers are found.""" if event.get("type") != "message": return msg = event["message"] # Don't reply to ourselves if msg["sender_email"] == client.email: return content = msg.get("content", "") tickers = TICKER_RE.findall(content.upper()) if not tickers: return lines = [fetch_price(t) for t in dict.fromkeys(tickers)] # dedup, preserve order reply = "\n".join(lines) client.send_message({ "type": msg["type"], # "stream" or "private" "to": msg["stream_id"] if msg["type"] == "stream" else msg["sender_email"], "topic": msg.get("subject", ""), "content": reply, }) def main(): client = zulip.Client(config_file=".zuliprc") print(f"slopbot running as {client.email} ...") client.call_on_each_event( lambda event: handle_event(event, client), event_types=["message"], ) if __name__ == "__main__": main()