Files
taro-bot-teams/bot.py
Björn Benouarets 7055272793 init: Initial commit
2026-01-07 06:20:16 +01:00

165 lines
7.1 KiB
Python

from typing import Tuple
from botbuilder.core import ActivityHandler, TurnContext
from botbuilder.schema import ChannelAccount, Mention
from config import DefaultConfig
from commands.webhook import WebhookManager
from modules.database import DatabaseManager
import re
class WebhookBot(ActivityHandler):
"""Webhook bot for Microsoft Teams."""
def __init__(self, config: DefaultConfig, database: DatabaseManager) -> None:
super().__init__()
self.command_prefix = DefaultConfig.COMMAND_PREFIX
self.webhook_manager = WebhookManager(database)
self.database = database
def is_command(self, text: str) -> tuple[bool, str | None]:
"""Check if the text is a command."""
if text.startswith(self.command_prefix) and len(text) > len(self.command_prefix):
return True, text[len(self.command_prefix):].split(" ")[0]
return False, None
def has_sub_command(self, text: str) -> tuple[bool, str | None]:
"""Check if the text has a sub command."""
split_text = text.split(" ")
if len(split_text) > 1:
return True, split_text[1]
return False, None
def is_bot_mentioned(self, turn_context: TurnContext) -> bool:
"""Check if the bot is mentioned in the activity."""
activity = turn_context.activity
# Get bot ID from recipient
if activity.recipient is None:
return False
bot_id = activity.recipient.id
# Check entities for mentions
if activity.entities is None:
return False
print(f"[Bot] Checking mentions, bot_id: {bot_id}, entities: {len(activity.entities)}")
for entity in activity.entities:
if entity.type == "mention":
try:
# Check if entity has mentioned property (ChannelAccount)
if hasattr(entity, 'mentioned') and entity.mentioned:
mentioned_id = getattr(entity.mentioned, 'id', None)
if mentioned_id == bot_id:
print(f"[Bot] Bot mentioned (via mentioned property)")
return True
# Check properties dict (alternative format)
if hasattr(entity, 'properties') and entity.properties:
props = entity.properties
if isinstance(props, dict):
mentioned = props.get('mentioned', {})
if isinstance(mentioned, dict):
if mentioned.get('id') == bot_id:
print(f"[Bot] Bot mentioned (via properties dict)")
return True
except Exception as e:
print(f"[Bot] Error checking mention entity: {e}")
continue
print(f"[Bot] Bot not mentioned")
return False
def is_channel_conversation(self, turn_context: TurnContext) -> bool:
"""Check if this is a channel conversation."""
activity = turn_context.activity
if activity.conversation is None:
return False
# In Teams, channel conversations have conversation_type == "channel"
return getattr(activity.conversation, 'conversation_type', None) == "channel"
def remove_mentions_from_text(self, text: str, activity) -> str:
"""Remove mention text from the message text."""
# Remove all between <at> and </at>
return re.sub(r'<at>.*?</at>', '', text)
async def on_command(self, command: str, turn_context: TurnContext) -> None:
"""Handle the command."""
await turn_context.send_activity(f"Command {command} received.")
return
async def on_members_added_activity(
self, members_added: list[ChannelAccount], turn_context: TurnContext
) -> None:
"""Handle the members added activity."""
if turn_context.activity.recipient is None:
return
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")
return
async def on_channel_created_activity(self, turn_context: TurnContext) -> None:
"""Handle the channel created activity."""
if turn_context.activity.recipient is None:
return
await turn_context.send_activity(f"Channel created: {turn_context.activity.channel_id}")
return
async def on_message_activity(self, turn_context: TurnContext) -> None:
"""Handle the message activity - works for both 1:1 and channel posts."""
activity = turn_context.activity
print(f"[Bot] on_message_activity called")
print(f"[Bot] Activity type: {activity.type}")
print(f"[Bot] Channel ID: {activity.channel_id}")
print(f"[Bot] Activity text: {activity.text}")
print(f"[Bot] Conversation type: {activity.conversation.conversation_type if activity.conversation else 'None'}")
print(f"[Bot] From: {activity.from_property.name if activity.from_property else 'None'}")
print(f"[Bot] Entities: {activity.entities}")
# Check if this is a channel conversation
is_channel = self.is_channel_conversation(turn_context)
print(f"[Bot] Is channel conversation: {is_channel}")
if not is_channel:
print(f"[Bot] Not a channel conversation, ignoring message")
return
# Get text from activity - can be None for channel posts with only attachments
text = activity.text or ""
# Remove mentions from text for processing
text = self.remove_mentions_from_text(text, activity)
# Strip whitespace and check if it's a command
text = text.strip()
print(f"[Bot] Processed text (after removing mentions): '{text}'")
is_command, command = self.is_command(text)
print(f"[Bot] Is command: {is_command}, Command: {command}")
if is_command:
match command:
case "webhooks":
is_sub_command, sub_command = self.has_sub_command(text)
if is_sub_command:
match sub_command:
case "list":
await self.webhook_manager.handle_list_webhooks(turn_context)
return
case _:
await turn_context.send_activity(f"Sub command {sub_command} not found.")
return
else:
# Default to list if no sub-command specified
await self.webhook_manager.handle_list_webhooks(turn_context)
return
case _:
await turn_context.send_activity(f"Command {command} not found.")
return
else:
print(f"[Bot] Not a command, echoing message")
await turn_context.send_activity(f"Message: {text}")
return