8000 Skills Lower layers · itsmokha/botbuilder-python@d056157 · GitHub
[go: up one dir, main page]

Skip to content

Commit d056157

Browse files
committed
Skills Lower layers
1 parent 6f81404 commit d056157

File tree

4 files changed

+682
-0
lines changed

4 files changed

+682
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for
5+
# license information.
6+
# --------------------------------------------------------------------------
7+
8+
from .bot_framework_http_client import BotFrameworkHttpClient
9+
from .channel_service_handler import ChannelServiceHandler
10+
from .skill_conversation_id_factory import SkillConversationIdFactory
11+
12+
__all__ = [
13+
"BotFrameworkHttpClient",
14+
"ChannelServiceHandler",
15+
"SkillConversationIdFactory",
16+
]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from aiohttp.web import RouteTableDef, Request, Response
5+
6+
from botbuilder.schema import Activity
7+
8+
from .channel_service_handler import ChannelServiceHandler
9+
10+
11+
async def deserialize_activity(request: Request) -> Activity:
12+
if "application/json" in request.headers["Content-Type"]:
13+
body = await request.json()
14+
else:
15+
return Response(status=415)
16+
17+
return Activity().deserialize(body)
18+
19+
20+
def channel_service_routes(handler: ChannelServiceHandler) -> RouteTableDef:
21+
routes = RouteTableDef()
22+
23+
@routes.post("/{conversation_id}/activities")
24+
async def send_to_conversation(request: Request):
25+
activity = await deserialize_activity(request)
26+
return await handler.handle_send_to_conversation(
27+
request.headers.get("Authorization"),
28+
request.match_info["conversation_id"],
29+
activity,
30+
)
31+
32+
@routes.post("/{conversation_id}/activities/{activity_id}")
33+
async def reply_to_activity(request: Request):
34+
activity = await deserialize_activity(request)
35+
return await handler.handle_reply_to_activity(
36+
request.headers.get("Authorization"),
37+
request.match_info["conversation_id"],
38+
request.match_info["activity_id"],
39+
activity,
40+
)
41+
42+
@routes.put("/{conversation_id}/activities/{activity_id}")
43+
async def update_activity(request: Request):
44+
activity E7F5 = await deserialize_activity(request)
45+
return await handler.handle_update_activity(
46+
request.headers.get("Authorization"),
47+
request.match_info["conversation_id"],
48+
request.match_info["activity_id"],
49+
activity,
50+
)
51+
52+
@routes.delete("/{conversation_id}/activities/{activity_id}")
53+
async def delete_activity(request: Request):
54+
return await handler.handle_delete_activity(
55+
request.headers.get("Authorization"),
56+
request.match_info["conversation_id"],
57+
request.match_info["activity_id"],
58+
)
59+
60+
@routes.get("/{conversation_id}/activities/{activity_id}/members")
61+
async def get_activity_members(request: Request):
62+
raise NotImplementedError("get_activity_members is not supported")
63+
64+
@routes.post("/")
65+
async def create_conversation(request: Request):
66+
raise NotImplementedError("create_conversation is not supported")
67+
68+
@routes.get("/")
69+
async def get_conversation(request: Request):
70+
raise NotImplementedError("get_conversation is not supported")
71+
72+
@routes.get("/{conversation_id}/members")
73+
async def get_conversation_members(request: Request):
74+
raise NotImplementedError("get_activity_members is not supported")
75+
76+
@routes.get("/{conversation_id}/pagedmembers")
77+
async def get_conversation_paged_members(request: Request):
78+
raise NotImplementedError("get_conversation_paged_members is not supported")
79+
80+
@routes.delete("/{conversation_id}/members/{member_id}")
81+
async def delete_conversation_members(request: Request):
82+
raise NotImplementedError("delete_conversation_members is not supported")
83+
84+
@routes.post("/{conversation_id}/activities/history")
85+
async def get_conversation_history(request: Request):
86+
raise NotImplementedError("get_conversation_history is not supported")
87+
88+
@routes.post("/{conversation_id}/attachments")
89+
async def upload_attachment(request: Request):
90+
raise NotImplementedError("upload_attachment is not supported")
91+
92+
return routes
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import json
5+
from typing import Dict
6+
from logging import Logger
7+
import aiohttp
8+
9+
from botbuilder.core import InvokeResponse
10+
from botbuilder.schema import Activity
11+
from botframework.connector.auth import (
12+
ChannelProvider,
13+
CredentialProvider,
14+
GovernmentConstants,
15+
MicrosoftAppCredentials,
16+
)
17+
18+
19+
class BotFrameworkHttpClient:
20+
21+
"""
22+
A skill host adapter implements API to forward activity to a skill and
23+
implements routing ChannelAPI calls from the Skill up through the bot/adapter.
24+
"""
25+
26+
INVOKE_ACTIVITY_NAME = "SkillEvents.ChannelApiInvoke"
27+
_BOT_IDENTITY_KEY = "BotIdentity"
28+
_APP_CREDENTIALS_CACHE: Dict[str:MicrosoftAppCredentials] = {}
29+
30+
def __init__(
31+
self,
32+
credential_provider: CredentialProvider,
33+
channel_provider: ChannelProvider = None,
34+
logger: Logger = None,
35+
):
36+
if not credential_provider:
37+
raise TypeError("credential_provider can't be None")
38+
39+
self._credential_provider = credential_provider
40+
self._channel_provider = channel_provider
41+
self._logger = logger
42+
self._session = aiohttp.ClientSession()
43+
44+
async def post_activity(
45+
self,
46+
from_bot_id: str,
47+
to_bot_id: str,
48+
to_url: str,
49+
service_url: str,
50+
conversation_id: str,
51+
activity: Activity,
52+
) -> InvokeResponse:
53+
app_credentials = await self._get_app_credentials(from_bot_id, to_bot_id)
54+
55+
if not app_credentials:
56+
raise RuntimeError("Unable to get appCredentials to connect to the skill")
57+
58+
# Get token for the skill call
59+
token = app_credentials.get_access_token()
60+
61+
# Capture current activity settings before changing them.
62+
# TODO: DO we need to set the activity ID? (events that are created manually don't have it).
63+
original_conversation_id = activity.conversation.id
64+
original_service_url = activity.service_url
65+
66+
try:
67+
activity.conversation.id = conversation_id
68+
activity.service_url = service_url
69+
70+
json_content = json.dumps(activity.serialize())
71+
resp = await self._session.post(
72+
to_url,
73+
data=json_content.encode("utf-8"),
74+
headers={
75+
"Authorization": f"Bearer:{token}",
76+
"Content-type": "application/json; charset=utf-8",
77+
},
78+
)
79+
resp.raise_for_status()
80+
content = await resp.json()
81+
82+
if content:
83+
return InvokeResponse(status=resp.status_code, body=content)
84+
85+
finally:
86+
# Restore activity properties.
87+
activity.conversation.id = original_conversation_id
88+
activity.service_url = original_service_url
89+
90+
async def _get_app_credentials(
91+
self, app_id: str, oauth_scope: str
92+
) -> MicrosoftAppCredentials:
93+
if not app_id:
94+
return MicrosoftAppCredentials(None, None)
95+
96+
cache_key = f"{app_id}{oauth_scope}"
97+
app_credentials = BotFrameworkHttpClient._APP_CREDENTIALS_CACHE.get(cache_key)
98+
99+
if app_credentials:
100+
return app_credentials
101+
102+
app_password = await self._credential_provider.get_app_password(app_id)
103+
app_credentials = MicrosoftAppCredentials(
104+
app_id, app_password, oauth_scope=oauth_scope
105+
)
106+
if self._channel_provider.is_government():
107+
app_credentials.oauth_endpoint = (
108+
GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
109+
)
110+
app_credentials.oauth_scope = (
111+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
112+
)
113+
114+
BotFrameworkHttpClient._APP_CREDENTIALS_CACHE[cache_key] = app_credentials
115+
return app_credentials

0 commit comments

Comments
 (0)
0