diff --git a/docker-compose.yml b/docker-compose.yml index 4cae27d..f41e373 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ services: steward: - image: ghcr.io/djw4/steward:latest + # image: ghcr.io/djw4/steward:latest # To build locally instead: uncomment the next line and comment out image above - # build: . - user: "1000:1000" # matches the UID/GID created in the Dockerfile - env_file: .env # copy .env.example → .env and fill in your values + build: . + user: "1000:1000" # matches the UID/GID created in the Dockerfile + env_file: .env # copy .env.example → .env and fill in your values environment: THREAD_MEMORY_PATH: /data/thread_memory.json volumes: diff --git a/steward/bot/telegram.py b/steward/bot/telegram.py index 4e2bb50..c7f6bcd 100644 --- a/steward/bot/telegram.py +++ b/steward/bot/telegram.py @@ -75,6 +75,13 @@ def _is_allowed(user_id: int, settings: Settings) -> bool: return user_id in settings.telegram_allowed_user_ids +def _is_group_enabled(chat_id: int, settings: Settings) -> bool: + """Return True if the group/channel is in the configured list (or no list is configured).""" + if not settings.telegram_group_ids: + return True + return chat_id in settings.telegram_group_ids + + async def _send_long(update: Update, text: str) -> None: """Send text, splitting across messages if it exceeds Telegram's 4096-char limit.""" limit = 4096 @@ -92,6 +99,10 @@ async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + await update.message.reply_text( # type: ignore[union-attr] "Hello, I'm *Steward* \U0001f916\n\n" "I'm your AI-assisted personal operations platform.\n" @@ -112,6 +123,10 @@ async def help_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + await update.message.reply_text( # type: ignore[union-attr] "*Steward commands*\n\n" "/start – greeting\n" @@ -136,6 +151,10 @@ async def clear_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + key = _thread_key(update) if key is not None: _thread_history[key].clear() @@ -167,6 +186,10 @@ async def flush_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + key = _thread_key(update) if key is None: await update.message.reply_text( # type: ignore[union-attr] @@ -246,6 +269,10 @@ async def recall_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + # If the user supplied a keyword query, search the knowledge base args: list[str] = context.args or [] # type: ignore[assignment] if args: @@ -306,13 +333,16 @@ async def analyse_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> if user is None or not _is_allowed(user.id, settings): return + chat = update.effective_chat + if chat is None or not _is_group_enabled(chat.id, settings): + return + await update.message.reply_text("Running analysis, please wait...") # type: ignore[union-attr] generator = ProposalGenerator(settings, llm) proposal = await generator.run() if proposal is None: await update.message.reply_text( # type: ignore[union-attr] - "Analysis could not be completed. " - "Check that `ANALYSIS_TARGET_URL` is configured." + "Analysis could not be completed. Check that `ANALYSIS_TARGET_URL` is configured." ) return @@ -356,8 +386,11 @@ async def message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> store: ThreadMemoryStore = context.bot_data["thread_store"] tool_client: ToolClient | None = context.bot_data.get("tool_client") user = update.effective_user + chat = update.effective_chat if user is None or not _is_allowed(user.id, settings): return + if chat is None or not _is_group_enabled(chat.id, settings): + return text = update.message.text # type: ignore[union-attr] if not text: @@ -402,6 +435,8 @@ def build_application( app.bot_data["llm"] = llm app.bot_data["thread_store"] = thread_store or ThreadMemoryStore(settings.thread_memory_path) app.bot_data["tool_client"] = tool_client # None when tools are not configured + logger.info("Configured allowed users: %s", settings.telegram_allowed_user_ids) + logger.info("Configured group IDs: %s", settings.telegram_group_ids) app.add_handler(CommandHandler("start", start_handler)) app.add_handler(CommandHandler("help", help_handler)) diff --git a/steward/config.py b/steward/config.py index aac14b5..a3fc3f3 100644 --- a/steward/config.py +++ b/steward/config.py @@ -11,6 +11,7 @@ class Settings(BaseSettings): # Telegram telegram_bot_token: str = "" telegram_allowed_user_ids: list[int] = [] + telegram_group_ids: list[int] = [] # LLM (OpenAI-compatible) openai_api_key: str = ""