diff --git a/.gitignore b/.gitignore
index 091964b..d45b969 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
.idea/
.venv/
__pycache__/
+devTools/
+*.log
.env
-CLAUDE.md
\ No newline at end of file
+CLAUDE.md
+database.db
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index ab1f416..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Ignored default folder with query files
-/queries/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 590a59e..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 45f24ab..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/taro-bot-teams.iml b/.idea/taro-bot-teams.iml
deleted file mode 100644
index c03f621..0000000
--- a/.idea/taro-bot-teams.iml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/bot.py b/bot.py
index 342c611..b685eca 100644
--- a/bot.py
+++ b/bot.py
@@ -8,6 +8,7 @@ from config import DefaultConfig
from commands.webhook import WebhookCommand
from commands.test import TestCommand
from modules.database import DatabaseManager
+from modules.command import Command
import re
@@ -20,11 +21,11 @@ class WebhookBot(ActivityHandler):
self.webhook_manager = WebhookCommand(database)
self.database = database
- def is_command(self, text: str) -> tuple[bool, str | None]:
+ def is_command(self, text: str) -> tuple[bool, str]:
"""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
+ return False, ""
def has_sub_command(self, text: str) -> tuple[bool, str | None]:
"""Check if the text has a sub command."""
@@ -117,10 +118,8 @@ class WebhookBot(ActivityHandler):
# 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
@@ -131,39 +130,13 @@ class WebhookBot(ActivityHandler):
# Strip whitespace and check if it's a command
text = text.strip()
- print(f"[Bot] Processed text (after removing mentions): '{text}'")
+ message = text.split(" ")[1:] if len(text.split(" ")) > 1 else None
+ message = " ".join(message) if message is not None else None
is_command, command = self.is_command(text)
- print(f"[Bot] Is command: {is_command}, Command: {command}")
if is_command:
- match command:
- case "test":
- # Get the text behind the command /test
- # Remove the command from the text
- message = text.replace(f"/test ", "").strip()
- if message == "":
- await turn_context.send_activity(f"Please provide a message for the test command.")
- return
- await TestCommand(self.database).handle_test(turn_context, message)
- return
- 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
+ cmd = Command(turn_context, self.database)
+ await cmd.handle_command(command, message)
else:
- print(f"[Bot] Not a command, echoing message")
await turn_context.send_activity(f"Message: {text}")
- return
\ No newline at end of file
+ return
\ No newline at end of file
diff --git a/commands/help.py b/commands/help.py
new file mode 100644
index 0000000..a135b2c
--- /dev/null
+++ b/commands/help.py
@@ -0,0 +1,44 @@
+from botbuilder.core import TurnContext, MessageFactory, CardFactory
+from botbuilder.schema import Attachment
+
+from modules.database import DatabaseManager
+from modules.template import TemplateEngine
+
+class HelpCommand:
+ def __init__(self, turn_context: TurnContext, database: DatabaseManager) -> None:
+ self.turn_context = turn_context
+ self.database = database
+
+ async def handle_help(self) -> None:
+ template = TemplateEngine("help-command-card")
+ card = template.json_dict({
+ "card_title": "Help",
+ "card_icon": "🚨",
+ "card_description": "You can use the following commands to interact with the bot:",
+ })
+ commands = [
+ {
+ "title": "**/help**",
+ "value": "Show this help message"
+ },
+ {
+ "title": "**/test** ",
+ "value": "Test the bot"
+ },
+ {
+ "title": "**/webhooks** create",
+ "value": "Create a new webhook"
+ },
+ {
+ "title": "**/webhooks** delete",
+ "value": "Delete a webhook"
+ },
+ {
+ "title": "**/webhooks** list",
+ "value": "List all webhooks"
+ }
+ ]
+ card["body"][2]["facts"] = commands
+ card_attachment = CardFactory.adaptive_card(card)
+ await self.turn_context.send_activity(MessageFactory.attachment(card_attachment))
+ return
\ No newline at end of file
diff --git a/commands/test.py b/commands/test.py
index d9c3167..1905525 100644
--- a/commands/test.py
+++ b/commands/test.py
@@ -10,32 +10,28 @@ class TestCommand:
def __init__(self, database: DatabaseManager):
self.database = database
- async def handle_test(self, turn_context: TurnContext, message: str) -> None:
+ async def handle_test(self, turn_context: TurnContext, command: str, message: str | None = None) -> None:
+ if message is None:
+ message = "No message provided"
try:
activity = turn_context.activity
- # conversation_reference = TurnContext.get_conversation_reference(turn_context.activity)
- # Check if the channel is a Microsoft Teams channel (msteams) or emulator (for testing)
channel_id_str = activity.channel_id
-
channel_data = activity.channel_data
- # channel_data is a dictionary, access it as such
- teams_id = channel_data.get('teamsTeamId') if channel_data and isinstance(channel_data, dict) else None
+ microsoft_teams_id_str = channel_data.get('teamsTeamId') if channel_data and isinstance(channel_data, dict) else None
microsoft_channel_id_str = channel_data.get('teamsChannelId') if channel_data and isinstance(channel_data, dict) else None
if channel_id_str == "emulator":
- teams_id = "00000000-0000-0000-0000-000000000000"
+ microsoft_teams_id_str = "00000000-0000-0000-0000-000000000000"
microsoft_channel_id_str = "00000000-0000-0000-0000-000000000000"
- # Load template and prepare data
template = TemplateEngine("card")
- # Create and send the Adaptive Card
- card_attachment = CardFactory.adaptive_card(template.generate({"card_type": "Test", "card_title": "Test", "card_content": message}))
+ card_attachment = CardFactory.adaptive_card(template.generate({"card_type": "Test", "card_icon": "🔎", "card_title": "Test", "card_content": f"_{message}_", "card_channel_id": microsoft_teams_id_str, "card_teams_id": microsoft_channel_id_str, "card_debug": json.dumps(channel_data, indent=4)}))
- # Send the attachment as a reply to the post in the channel
- result = await turn_context.send_activity(MessageFactory.attachment(card_attachment))
+ await turn_context.send_activity(MessageFactory.attachment(card_attachment))
+ return
except Exception as e:
import traceback
traceback.print_exc()
diff --git a/commands/webhook.py b/commands/webhook.py
index 634ec9f..be0df92 100644
--- a/commands/webhook.py
+++ b/commands/webhook.py
@@ -16,29 +16,23 @@ class WebhookCommand:
async def handle_list_webhooks(self, turn_context: TurnContext) -> None:
try:
activity = turn_context.activity
- # conversation_reference = TurnContext.get_conversation_reference(turn_context.activity)
- # Check if the channel is a Microsoft Teams channel (msteams) or emulator (for testing)
channel_id_str = activity.channel_id
-
channel_data = activity.channel_data
- # channel_data is a dictionary, access it as such
- teams_id = channel_data.get('teamsTeamId') if channel_data and isinstance(channel_data, dict) else None
+ microsoft_teams_id_str = channel_data.get('teamsTeamId') if channel_data and isinstance(channel_data, dict) else None
microsoft_channel_id_str = channel_data.get('teamsChannelId') if channel_data and isinstance(channel_data, dict) else None
if channel_id_str == "emulator":
- teams_id = "00000000-0000-0000-0000-000000000000"
+ microsoft_teams_id_str = "00000000-0000-0000-0000-000000000000"
microsoft_channel_id_str = "00000000-0000-0000-0000-000000000000"
- # Get the actual channel UUID from the database
channel_uuid = None
webhook_count = 0
if microsoft_channel_id_str:
try:
from uuid import UUID
- # Try to get channel by Microsoft Teams channel ID
microsoft_channel_id = UUID(microsoft_channel_id_str) if isinstance(microsoft_channel_id_str, str) else microsoft_channel_id_str
channel_obj = self.database.get_channel_by_microsoft_channel_id(str(microsoft_channel_id))
if channel_obj:
@@ -47,14 +41,10 @@ class WebhookCommand:
except (ValueError, TypeError) as e:
webhook_count = 0
- # Load template and prepare data
template = TemplateEngine("webhook-overview-card" if webhook_count > 0 else "webhook-overview-no-webhooks-card")
-
- # Create and send the Adaptive Card
card_attachment = self.create_webhooks_card(template, {"webhook_count": webhook_count})
-
- # Send the attachment as a reply to the post in the channel
- result = await turn_context.send_activity(MessageFactory.attachment(card_attachment))
+ await turn_context.send_activity(MessageFactory.attachment(card_attachment))
+ return
except Exception as e:
import traceback
traceback.print_exc()
diff --git a/database.db b/database.db
deleted file mode 100644
index 0f5606a..0000000
Binary files a/database.db and /dev/null differ
diff --git a/devTools/m365agentsplayground.log b/devTools/m365agentsplayground.log
deleted file mode 100644
index 729f3de..0000000
--- a/devTools/m365agentsplayground.log
+++ /dev/null
@@ -1,76 +0,0 @@
-debug Telemetry: agents-playground-cli/cliStart {"cleanProperties":{"isExec":"false","argv":",-e,http://localhost:3978/api/messages,-c,emulator"}}
-
-log Listening on 56150
-log Microsoft 365 Agents Playground is being launched for you to debug the app: http://localhost:56150
-debug started web socket client
-debug started web socket client
-log Waiting for connection of endpoint: http://localhost:3978/api/messages
-log waiting for 1 resources: http://localhost:3978/api/messages
-log wait-on(76805) complete
-log Events recording disabled.
-debug Telemetry: agents-playground-server/eventsRecording {"cleanProperties":{"enabled":"false"}}
-
-debug Telemetry: agents-playground-server/getConfig {"cleanProperties":{"internalConfig":"{\"locale\":\"en-US\",\"localTimezone\":\"Europe/Berlin\",\"channelId\":\"emulator\",\"debugConfig\":{\"eventsRecordingEnabled\":false}}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"40b98e9d-79b0-4d5f-b810-32da23f6b42e","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"team-id","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"message","conversationId":"team-id;messageid=1767856906530","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug stopping web socket client
-debug stopping web socket client
-debug started web socket client
-debug started web socket client
-log Waiting for connection of endpoint: http://localhost:3978/api/messages
-log waiting for 1 resources: http://localhost:3978/api/messages
-log wait-on(76805) complete
-log Events recording disabled.
-debug Telemetry: agents-playground-server/eventsRecording {"cleanProperties":{"enabled":"false"}}
-
-debug Telemetry: agents-playground-server/getConfig {"cleanProperties":{"internalConfig":"{\"locale\":\"en-US\",\"localTimezone\":\"Europe/Berlin\",\"channelId\":\"emulator\",\"debugConfig\":{\"eventsRecordingEnabled\":false}}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"40b98e9d-79b0-4d5f-b810-32da23f6b42e","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"team-id","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"message","conversationId":"team-id;messageid=1767857222140","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug stopping web socket client
-debug stopping web socket client
-debug started web socket client
-debug started web socket client
-log Waiting for connection of endpoint: http://localhost:3978/api/messages
-log waiting for 1 resources: http://localhost:3978/api/messages
-log wait-on(76805) complete
-log Events recording disabled.
-debug Telemetry: agents-playground-server/eventsRecording {"cleanProperties":{"enabled":"false"}}
-
-debug Telemetry: agents-playground-server/getConfig {"cleanProperties":{"internalConfig":"{\"locale\":\"en-US\",\"localTimezone\":\"Europe/Berlin\",\"channelId\":\"emulator\",\"debugConfig\":{\"eventsRecordingEnabled\":false}}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"40b98e9d-79b0-4d5f-b810-32da23f6b42e","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"team-id","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"message","conversationId":"team-id;messageid=1767857462550","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug stopping web socket client
-debug stopping web socket client
-debug started web socket client
-debug started web socket client
-log Waiting for connection of endpoint: http://localhost:3978/api/messages
-log waiting for 1 resources: http://localhost:3978/api/messages
-log wait-on(76805) complete
-log Events recording disabled.
-debug Telemetry: agents-playground-server/eventsRecording {"cleanProperties":{"enabled":"false"}}
-
-debug Telemetry: agents-playground-server/getConfig {"cleanProperties":{"internalConfig":"{\"locale\":\"en-US\",\"localTimezone\":\"Europe/Berlin\",\"channelId\":\"emulator\",\"debugConfig\":{\"eventsRecordingEnabled\":false}}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"40b98e9d-79b0-4d5f-b810-32da23f6b42e","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"conversationUpdate","conversationId":"team-id","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug Telemetry: agents-playground-server/sendActivity {"cleanProperties":{"activityType":"message","conversationId":"team-id;messageid=1767857505932","headers":"{\"x-ms-agents-playground\":\"true\"}"}}
-
-debug stopping web socket client
-debug stopping web socket client
diff --git a/modules/command.py b/modules/command.py
new file mode 100644
index 0000000..38cb71d
--- /dev/null
+++ b/modules/command.py
@@ -0,0 +1,20 @@
+from botbuilder.core import TurnContext
+
+from commands.test import TestCommand
+from commands.webhook import WebhookCommand
+from commands.help import HelpCommand
+from modules.database import DatabaseManager
+
+class Command:
+ def __init__(self, turn_context: TurnContext, database: DatabaseManager) -> None:
+ self.turn_context = turn_context
+ self.database = database
+
+ async def handle_command(self, command: str, message: str | None = None) -> None:
+ match command:
+ case "test":
+ await TestCommand(self.database).handle_test(self.turn_context, command, message)
+ case "webhooks":
+ await WebhookCommand(self.database).handle_list_webhooks(self.turn_context)
+ case _:
+ await HelpCommand(self.turn_context, self.database).handle_help()
\ No newline at end of file
diff --git a/modules/database.py b/modules/database.py
index 162432e..b37d938 100644
--- a/modules/database.py
+++ b/modules/database.py
@@ -59,24 +59,19 @@ class DatabaseManager:
def get_webhooks(self) -> list[Webhook]:
return self.__session.query(Webhook).all()
- def count_webhooks(self) -> int:
- return len(self.get_webhooks())
-
def get_webhook_by_id(self, id: str) -> Webhook:
webhook = self.__session.query(Webhook).filter(Webhook.id == id).first()
if webhook is None:
raise ValueError(f"Webhook with id {id} not found")
return webhook
- def get_services(self) -> list[Service]:
- return self.__session.query(Service).all()
-
- def get_service_by_id(self, id: str) -> Service:
- service = self.__session.query(Service).filter(Service.id == id).first()
- if service is None:
- raise ValueError(f"Service with id {id} not found")
- return service
-
+ def count_webhooks_by_channel_id(self, channel_id: str | UUID) -> int | None:
+ try:
+ channel_uuid = UUID(channel_id) if isinstance(channel_id, str) else channel_id
+ return len(self.__session.query(Webhook).filter(Webhook.channel_id == channel_uuid).all())
+ except (ValueError, TypeError):
+ return None
+
def get_channels(self) -> list[Channel]:
return self.__session.query(Channel).all()
@@ -86,22 +81,17 @@ class DatabaseManager:
raise ValueError(f"Channel with id {id} not found")
return channel
- def get_channel_by_microsoft_channel_id(self, microsoft_channel_id: str) -> Channel | None:
- """Get channel by Microsoft Teams channel ID. Returns None if not found."""
- try:
- # Convert string to UUID if needed
- channel_uuid = UUID(microsoft_channel_id) if isinstance(microsoft_channel_id, str) else microsoft_channel_id
- channel = self.__session.query(Channel).filter(Channel.microsoft_channel_id == channel_uuid).first()
- return channel
- except (ValueError, TypeError):
- return None
+ def get_channel_by_microsoft_channel_id(self, microsoft_channel_id: str) -> Channel:
+ channel = self.__session.query(Channel).filter(Channel.microsoft_channel_id == microsoft_channel_id).first()
+ if channel is None:
+ raise ValueError(f"Channel with microsoft channel id {microsoft_channel_id} not found")
+ return channel
- def count_webhooks_by_channel_id(self, channel_id: str | UUID) -> int:
- """Count webhooks by channel ID. Accepts UUID string or UUID object."""
- try:
- # Convert string to UUID if needed
- channel_uuid = UUID(channel_id) if isinstance(channel_id, str) else channel_id
- return len(self.__session.query(Webhook).filter(Webhook.channel_id == channel_uuid).all())
- except (ValueError, TypeError):
- # If channel_id is not a valid UUID, return 0
- return 0
\ No newline at end of file
+ def get_services(self) -> list[Service]:
+ return self.__session.query(Service).all()
+
+ def get_service_by_id(self, id: str) -> Service:
+ service = self.__session.query(Service).filter(Service.id == id).first()
+ if service is None:
+ raise ValueError(f"Service with id {id} not found")
+ return service
\ No newline at end of file
diff --git a/modules/template.py b/modules/template.py
index a3478dd..3d4c63e 100644
--- a/modules/template.py
+++ b/modules/template.py
@@ -2,6 +2,7 @@ import os
import json
from typing import Any
+from botbuilder.schema import Attachment
class TemplateEngine:
def __init__(self, template_name: str, template_path: str = "templates/") -> None:
self.__name = template_name
@@ -23,7 +24,22 @@ class TemplateEngine:
return obj.format(**data)
else:
return obj
-
- def generate(self, data: dict) -> dict[str, Any]:
+
+ def __generate_card(self, data: dict) -> dict[str, Any]:
template = self.__load_template()
- return self.__replace_placeholders(template, data)
\ No newline at end of file
+ return self.__replace_placeholders(template, data)
+
+ def generate(self, data: dict) -> dict[str, Any]:
+ return self.__generate_card(data)
+
+ def attachment(self, data: dict) -> Attachment:
+ return Attachment(
+ content_type="application/vnd.microsoft.card.adaptive",
+ content=self.generate(data)
+ )
+
+ def json_dict(self, data: dict) -> dict:
+ return self.generate(data)
+
+ def json(self, data: dict) -> str:
+ return json.dumps(self.generate(data))
\ No newline at end of file
diff --git a/templates/card.json b/templates/card.json
index eec7c6e..ddcae5e 100644
--- a/templates/card.json
+++ b/templates/card.json
@@ -14,12 +14,31 @@
"wrap": true,
"style": "heading",
"size": "ExtraLarge",
- "text": "{card_title}"
+ "text": "{card_icon} {card_title}"
},
{
"type": "TextBlock",
"text": "{card_content}",
"wrap": true
+ },
+ {
+ "type": "FactSet",
+ "facts": [
+ {
+ "title": "Channel",
+ "value": "{card_channel_id}"
+ },
+ {
+ "title": "Teams",
+ "value": "{card_teams_id}"
+ }
+ ]
+ },
+ {
+ "type": "TextBlock",
+ "text": "{card_debug}",
+ "wrap": true,
+ "fontType": "Monospace"
}
]
}
\ No newline at end of file
diff --git a/templates/help-command-card.json b/templates/help-command-card.json
new file mode 100644
index 0000000..dfed599
--- /dev/null
+++ b/templates/help-command-card.json
@@ -0,0 +1,23 @@
+{
+ "type": "AdaptiveCard",
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "version": "1.5",
+ "body": [
+ {
+ "type": "TextBlock",
+ "text": "{card_icon} {card_title}",
+ "wrap": true,
+ "style": "heading",
+ "size": "ExtraLarge"
+ },
+ {
+ "type": "TextBlock",
+ "text": "{card_description}",
+ "wrap": true
+ },
+ {
+ "type": "FactSet",
+ "facts": []
+ }
+ ]
+}
\ No newline at end of file