-
Notifications
You must be signed in to change notification settings - Fork 13
Simply HttpPlugin to only focus on server-concepts #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
heyitsaamir
wants to merge
3
commits into
main
Choose a base branch
from
aamirj/simplifyHttp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Proactive Messaging Example | ||
|
|
||
| This example demonstrates how to send proactive messages to Teams users without running a server. This is useful for: | ||
| - Scheduled notifications | ||
| - Alert systems | ||
| - Background jobs that need to notify users | ||
| - Webhook handlers that send messages | ||
|
|
||
| ## Key Concepts | ||
|
|
||
| - Uses `app.initialize()` instead of `app.start()` (no HTTP server) | ||
| - Directly sends messages using `app.send()` | ||
| - Requires a conversation ID (from previous interactions or from the Teams API) | ||
|
|
||
| ## How It Works | ||
|
|
||
| The example shows the separation of activity sending from HTTP transport: | ||
|
|
||
| 1. **Initialize without server**: `await app.initialize()` sets up credentials, token manager, and activity sender without starting the HTTP server | ||
| 2. **Send messages**: `await app.send(conversation_id, message)` sends messages directly using the ActivitySender | ||
| 3. **No HTTP server**: Perfect for background jobs, scheduled tasks, or webhook handlers | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| # Set up your environment variables | ||
| export CLIENT_ID=your_app_id | ||
| export CLIENT_SECRET=your_app_secret | ||
| export TENANT_ID=your_tenant_id | ||
|
|
||
| # Run the example with a conversation ID | ||
| uv run src/main.py <conversation_id> | ||
| ``` | ||
|
|
||
| ## Getting a Conversation ID | ||
|
|
||
| You need a conversation ID to send proactive messages. You can get this from: | ||
|
|
||
| 1. **Previous bot interactions**: Store the conversation ID when users first interact with your bot | ||
| 2. **Teams API**: Use the Microsoft Teams API to create or get conversation references | ||
| 3. **Testing**: Use an existing bot conversation and extract the conversation ID from the activity | ||
|
|
||
| ## Example Output | ||
|
|
||
| ``` | ||
| Initializing app (without starting server)... | ||
| ✓ App initialized | ||
|
|
||
| Sending proactive message to conversation: 19:... | ||
| Message: Hello! This is a proactive message sent without a running server 🚀 | ||
| ✓ Message sent successfully! Activity ID: 1234567890 | ||
|
|
||
| Sending proactive card to conversation: 19:... | ||
| ✓ Card sent successfully! Activity ID: 1234567891 | ||
|
|
||
| ✓ All proactive messages sent successfully! | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [project] | ||
| name = "proactive-messaging" | ||
| version = "0.1.0" | ||
| description = "Example showing proactive messaging without running a server" | ||
| readme = "README.md" | ||
| requires-python = ">=3.12,<3.14" | ||
| dependencies = [ | ||
| "dotenv>=0.9.9", | ||
| "microsoft-teams-apps", | ||
| ] | ||
|
|
||
| [tool.uv.sources] | ||
| microsoft-teams-apps = { workspace = true } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| """ | ||
| Copyright (c) Microsoft Corporation. All rights reserved. | ||
| Licensed under the MIT License. | ||
|
|
||
| Proactive Messaging Example | ||
| =========================== | ||
| This example demonstrates how to send proactive messages to Teams users | ||
| without running a server. This is useful for: | ||
| - Scheduled notifications | ||
| - Alert systems | ||
| - Background jobs that need to notify users | ||
| - Webhook handlers that send messages | ||
|
|
||
| Key points: | ||
| - Uses app.initialize() instead of app.start() (no HTTP server) | ||
| - Directly sends messages using app.send() | ||
| - Requires a conversation ID (from previous interactions or from the Teams API) | ||
| """ | ||
|
|
||
| import argparse | ||
| import asyncio | ||
|
|
||
| from microsoft_teams.apps import App | ||
| from microsoft_teams.cards import ActionSet, AdaptiveCard, OpenUrlAction, TextBlock | ||
|
|
||
|
|
||
| async def send_proactive_message(app: App, conversation_id: str, message: str) -> None: | ||
| """ | ||
| Send a proactive message to a Teams conversation. | ||
|
|
||
| Args: | ||
| app: The initialized App instance | ||
| conversation_id: The Teams conversation ID to send the message to | ||
| message: The message text to send | ||
| """ | ||
| print(f"Sending proactive message to conversation: {conversation_id}") | ||
| print(f"Message: {message}") | ||
|
|
||
| # Send the message | ||
| result = await app.send(conversation_id, message) | ||
|
|
||
| print(f"✓ Message sent successfully! Activity ID: {result.id}") | ||
|
|
||
|
|
||
| async def send_proactive_card(app: App, conversation_id: str) -> None: | ||
| """ | ||
| Send a proactive Adaptive Card to a Teams conversation. | ||
|
|
||
| Args: | ||
| app: The initialized App instance | ||
| conversation_id: The Teams conversation ID to send the card to | ||
| """ | ||
| # Create an Adaptive Card | ||
| card = AdaptiveCard( | ||
| schema="http://adaptivecards.io/schemas/adaptive-card.json", | ||
| body=[ | ||
| TextBlock(text="Proactive Notification", size="Large", weight="Bolder"), | ||
| TextBlock(text="This message was sent proactively without a server running!", wrap=True), | ||
| TextBlock(text="Status: Active • Priority: High • Time: Now", wrap=True, is_subtle=True), | ||
| ActionSet(actions=[OpenUrlAction(title="Learn More", url="https://aka.ms/teams-sdk")]), | ||
| ], | ||
| ) | ||
|
|
||
| print(f"Sending proactive card to conversation: {conversation_id}") | ||
|
|
||
| result = await app.send(conversation_id, card) | ||
|
|
||
| print(f"✓ Card sent successfully! Activity ID: {result.id}") | ||
|
|
||
|
|
||
| async def main(): | ||
| """ | ||
| Main function demonstrating proactive messaging. | ||
|
|
||
| In a real application, you would: | ||
| 1. Store conversation IDs when users first interact with your bot | ||
| 2. Use those IDs later to send proactive messages | ||
| 3. Get conversation IDs from the Teams API or from previous interactions | ||
| """ | ||
| parser = argparse.ArgumentParser( | ||
| description="Send proactive messages to a Teams conversation without running a server" | ||
| ) | ||
| parser.add_argument("conversation_id", help="The Teams conversation ID to send messages to") | ||
| args = parser.parse_args() | ||
|
|
||
| # Create app (no plugins needed for sending only) | ||
| app = App() | ||
|
|
||
| # Initialize the app without starting the HTTP server | ||
| # This sets up credentials, token manager, and activity sender | ||
| print("Initializing app (without starting server)...") | ||
| await app.initialize() | ||
| print("✓ App initialized\n") | ||
|
|
||
| # Example 1: Send a simple text message | ||
| await send_proactive_message( | ||
| app, args.conversation_id, "Hello! This is a proactive message sent without a running server 🚀" | ||
| ) | ||
|
|
||
| # Wait a bit between messages | ||
| await asyncio.sleep(2) | ||
|
|
||
| # Example 2: Send an Adaptive Card | ||
| await send_proactive_card(app, args.conversation_id) | ||
|
|
||
| print("\n✓ All proactive messages sent successfully!") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| """ | ||
| Copyright (c) Microsoft Corporation. All rights reserved. | ||
| Licensed under the MIT License. | ||
| """ | ||
|
|
||
| from logging import Logger | ||
| from typing import Optional | ||
|
|
||
| from microsoft_teams.api import ( | ||
| ActivityParams, | ||
| ApiClient, | ||
| ConversationReference, | ||
| SentActivity, | ||
| ) | ||
| from microsoft_teams.common import Client, ConsoleLogger | ||
|
|
||
| from .http_stream import HttpStream | ||
| from .plugins.streamer import StreamerProtocol | ||
|
|
||
|
|
||
| class ActivitySender: | ||
| """ | ||
| Handles sending activities to the Bot Framework. | ||
| Separate from transport concerns (HTTP, WebSocket, etc.) | ||
| """ | ||
|
|
||
| def __init__(self, client: Client, logger: Optional[Logger] = None): | ||
| """ | ||
| Initialize ActivitySender. | ||
|
|
||
| Args: | ||
| client: HTTP client with token provider configured | ||
| logger: Optional logger instance for debugging. If not provided, creates a default console logger. | ||
| """ | ||
| self._client = client | ||
| self._logger = logger or ConsoleLogger().create_logger("@teams/activity-sender") | ||
|
|
||
| async def send(self, activity: ActivityParams, ref: ConversationReference) -> SentActivity: | ||
| """ | ||
| Send an activity to the Bot Framework. | ||
|
|
||
| Args: | ||
| activity: The activity to send | ||
| ref: The conversation reference | ||
|
|
||
| Returns: | ||
| The sent activity with id and other server-populated fields | ||
| """ | ||
| # Create API client for this conversation's service URL | ||
| api = ApiClient(service_url=ref.service_url, options=self._client) | ||
|
|
||
| # Merge activity with conversation reference | ||
| activity.from_ = ref.bot | ||
| activity.conversation = ref.conversation | ||
|
|
||
| # Decide create vs update | ||
| if hasattr(activity, "id") and activity.id: | ||
| res = await api.conversations.activities(ref.conversation.id).update(activity.id, activity) | ||
| return SentActivity.merge(activity, res) | ||
|
|
||
| res = await api.conversations.activities(ref.conversation.id).create(activity) | ||
| return SentActivity.merge(activity, res) | ||
|
|
||
| def create_stream(self, ref: ConversationReference) -> StreamerProtocol: | ||
| """ | ||
| Create a new activity stream for real-time updates. | ||
|
|
||
| Args: | ||
| ref: The conversation reference | ||
|
|
||
| Returns: | ||
| A new streaming instance | ||
| """ | ||
| # Create API client for this conversation's service URL | ||
| api = ApiClient(ref.service_url, self._client) | ||
| return HttpStream(api, ref, self._logger) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.