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 and
return re.sub(r'.*?', '', 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