feat: add group/channel support for Telegram bot
- Add TELEGRAM_GROUP_IDS configuration setting to enable optional group/channel filtering - Add _is_group_enabled() authorization check to all command handlers - Add group/channel authorization check to message_handler - All handlers now verify both user authorization and group authorization - If TELEGRAM_GROUP_IDS is not configured, bot accepts messages from any group - If TELEGRAM_GROUP_IDS is configured, bot only processes messages from those groups This enables the bot to be used in group chats and channels with Topics/Threads support.
This commit is contained in:
parent
a3ca696546
commit
669b57a317
@ -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:
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 = ""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user