Skip to content
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

on_teams_file_consent_accept not being triggered #2949

Closed
JoshBello opened this issue Nov 23, 2020 · 0 comments
Closed

on_teams_file_consent_accept not being triggered #2949

JoshBello opened this issue Nov 23, 2020 · 0 comments
Assignees
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. bug Indicates an unexpected problem or an unintended behavior. customer-reported Issue is created by anyone that is not a collaborator in the repository. needs-triage The issue has just been created and it has not been reviewed by the team.

Comments

@JoshBello
Copy link

JoshBello commented Nov 23, 2020

Sample information

  1. Sample type: python
  2. Sample language: python3
  3. Sample name: 56.teams-file-upload

Describe the bug

When proactively sending a file to the user. The file consent card is displayed but when the user accepts there is an error:

Screenshot 2020-11-23 at 16 17 27

The sample works fine and when I compare the JSON responses they look the same.

app.py Code:

from typing import Dict
import json
import sys
import traceback
import pickle
from datetime import datetime
from http import HTTPStatus
from aiohttp import web
from aiohttp.web import Request, Response, json_response
from botbuilder.core import (
    BotFrameworkAdapter,
    BotFrameworkAdapterSettings,
    ConversationState,
    MemoryStorage,
    TurnContext,
    UserState,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes, ConversationReference

from bot import DialogAndWelcomeBot

from config import DefaultConfig
from dialogs import MinutesDialog

#Config class in config.py file, contains appid & app secret for bot auth
CONFIG = DefaultConfig()

# Create bot middleware adapter for bot auth
SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD)
ADAPTER = BotFrameworkAdapter(SETTINGS)


#This function is for dev purposes only, if there's an internal server error it will display bot encountered bug to user
async def on_error(context: TurnContext, error: Exception):

    print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
    traceback.print_exc()

    # Send a message to the user
    await context.send_activity("The bot encountered an error or bug.")
    await context.send_activity(
        "To continue to run this bot, please fix the bot source code."
    )


ADAPTER.on_turn_error = on_error

# Create MemoryStorage, Userstate object & conversationstate object
MEMORY = MemoryStorage()
USER_STATE = UserState(MEMORY)
CONVERSATION_STATE = ConversationState(MEMORY)
#Dialog class assigned to minutesdialog class defined in dialogs/minutesdialog.py
#Bot class assigned to bot class in bots/DialogandWelcomeBot
DIALOG = MinutesDialog(USER_STATE, CONFIG.CONNECTION_NAME)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)


# Listen for incoming requests on /api/messages.
async def messages(req: Request) -> Response:
    # Main bot message handler.
    #body in the POST req header contains the messages from user
    if "application/json" in req.headers["Content-Type"]:
        body = await req.json()
        print(body)
    else:
        return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
    #Activity is the ms preferred format which the botbuilder sdk can read
    activity = Activity().deserialize(body)
    logger.warning(body)
    auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
    logger.warning(auth_header)
    #invoke response triggers the bot which in turn triggers the dialog.
    invoke_response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
    logger.warning(invoke_response)

    if invoke_response:
        return json_response(data=invoke_response.body, status=invoke_response.status)
    return Response(status=HTTPStatus.OK)


# to trigger bot: https://{domain}/api/messages
APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)

if __name__ == "__main__":
    try:
        web.run_app(APP, host="localhost", port=CONFIG.PORT)
    except Exception as error:
        raise error

dialog.py Code:

import json
import os.path

from typing import List
from botbuilder.core import (
    ConversationState,
    MessageFactory,
    UserState,
    TurnContext,
    ActivityHandler
)

from botbuilder.core.teams import TeamsActivityHandler

import requests
from botbuilder.dialogs import Dialog
from typing import Dict
from botbuilder.schema import (
    Activity,
    ChannelAccount,
    ActivityTypes,
    ConversationAccount,
    Attachment,
)

from helpers.dialoghelper import DialogHelper

from botbuilder.schema.teams import (
    TeamInfo,
    TeamsChannelAccount,
    FileDownloadInfo,
    FileConsentCard,
    FileConsentCardResponse,
    FileInfoCard,
)

from botbuilder.schema.teams.additional_properties import ContentType

from botbuilder.core.teams import TeamsActivityHandler

from google.cloud import storage

gcp_credentials = os.path.join(os.path.dirname(__file__), 'GCP_MMA.json')
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = gcp_credentials


# Furthers the Dialog bot and adds the welcome message event, also triggers the add_conversation_reference -> which is only needed for proactive messaged.
class DialogAndWelcomeBot(TeamsActivityHandler):
    def __init__(
        self,
        conversation_state: ConversationState,
        user_state: UserState,
        dialog: Dialog,
    ):
        self.conversation_state = conversation_state
        self.user_state = user_state
        self.dialog = dialog



    async def on_conversation_update_activity(self, turn_context: TurnContext):
        return await super().on_conversation_update_activity(turn_context)


    # Oauth2 built in function
    async def on_teams_signin_verify_state(self, turn_context: TurnContext):
        # Run the Dialog with the new Token Response Event Activity.
        # The OAuth Prompt needs to see the Invoke Activity in order to complete the login process.
        await DialogHelper.run_dialog(
            self.dialog,
            turn_context,
            self.conversation_state.create_property("DialogState"),
        )

    # Welcome message function
    async def on_teams_members_added(  # pylint: disable=unused-argument
        self,
        teams_members_added: [TeamsChannelAccount],
        team_info: TeamInfo,
        turn_context: TurnContext,
    ):
        #opening message to user
        for member in teams_members_added:
            if member.id != turn_context.activity.recipient.id:
                await turn_context.send_activity(
                    f"Type anything to start!"
                )

    #on turn triggers the conversation state to change
    async def on_turn(self, turn_context: TurnContext):
        await super().on_turn(turn_context)
        # Save any state changes that might have occurred during the turn.
        await self.conversation_state.save_changes(turn_context, False)
        await self.user_state.save_changes(turn_context, False)

    #when message received, dialog
    async def on_message_activity(self, turn_context: TurnContext):
        await DialogHelper.run_dialog(
            self.dialog,
            turn_context,
            self.conversation_state.create_property("DialogState"),
        )

    async def _send_file_card(
            self, turn_context: TurnContext, filename: str, file_size: int
    ):
        """
        Send a FileConsentCard to get permission from the user to upload a file.
        """

        consent_context = {"filename": filename}

        file_card = FileConsentCard(
            description="This is the file I want to send you",
            size_in_bytes=file_size,
            accept_context=consent_context,
            decline_context=consent_context
        )

        as_attachment = Attachment(
            content=file_card.serialize(), content_type=ContentType.FILE_CONSENT_CARD, name=filename
        )

        reply_activity = self._create_reply(turn_context.activity)
        reply_activity.attachments = [as_attachment]
        await turn_context.send_activity(reply_activity)


    async def on_teams_file_consent_accept(
            self,
            turn_context: TurnContext,
            file_consent_card_response: FileConsentCardResponse,
            filename : str
    ):
        """
        The user accepted the file upload request.  Do the actual upload now.
        """

        print('Accepted!!!!\n')
        await turn_context.send_activity('Accepted!!!!')

        filename = file_consent_card_response.context["filename"]

        file_path = "files/" + file_consent_card_response.context["filename"]
        await turn_context.send_activity(file_path)


        file_size = os.path.getsize(file_path)
        print(file_size)

        headers = {
            "Content-Length": f"\"{file_size}\"",
            "Content-Range": f"bytes 0-{file_size-1}/{file_size}"
        }
        print(headers)
        response = requests.put(
            file_consent_card_response.upload_info.upload_url, open(file_path, "rb"), headers=headers
        )
        print(response)

        if response.status_code != 201:
            print(f"Failed to upload, status {response.status_code}, file_path={file_path}")
            await self._file_upload_failed(turn_context, "Unable to upload file.")
        else:
            await self._file_upload_complete(turn_context, file_consent_card_response)


    async def on_teams_file_consent_decline(
            self,
            turn_context: TurnContext,
            file_consent_card_response: FileConsentCardResponse
    ):
        """
        The user declined the file upload.
        """

        context = file_consent_card_response.context

        reply = self._create_reply(
            turn_context.activity,
            f"Declined. We won't upload file <b>{context['filename']}</b>.",
            "xml"
        )
        await turn_context.send_activity(reply)
        await turn_context.send_activity(reply)

    async def _file_upload_complete(
            self,
            turn_context: TurnContext,
            file_consent_card_response: FileConsentCardResponse
    ):
        """
        The file was uploaded, so display a FileInfoCard so the user can view the
        file in Teams.
        """

        name = file_consent_card_response.upload_info.name

        download_card = FileInfoCard(
            unique_id=file_consent_card_response.upload_info.unique_id,
            file_type=file_consent_card_response.upload_info.file_type
        )

        as_attachment = Attachment(
            content=download_card.serialize(),
            content_type=ContentType.FILE_INFO_CARD,
            name=name,
            content_url=file_consent_card_response.upload_info.content_url
        )

        reply = self._create_reply(
            turn_context.activity,
            f"<b>File uploaded.</b> Your file <b>{name}</b> is ready to download",
            "xml"
        )
        reply.attachments = [as_attachment]

        await turn_context.send_activity(reply)

    async def _file_upload_failed(self, turn_context: TurnContext, error: str):
        reply = self._create_reply(
            turn_context.activity,
            f"<b>File upload failed.</b> Error: <pre>{error}</pre>",
            "xml"
        )
        await turn_context.send_activity(reply)

    def _create_reply(self, activity, text=None, text_format=None):
        return Activity(
            type=ActivityTypes.message,
            timestamp=datetime.utcnow(),
            from_property=ChannelAccount(
                id=activity.recipient.id, name=activity.recipient.name
            ),
            recipient=ChannelAccount(
                id=activity.from_property.id, name=activity.from_property.name
            ),
            reply_to_id=activity.id,
            service_url=activity.service_url,
            channel_id=activity.channel_id,
            conversation=ConversationAccount(
                is_group=activity.conversation.is_group,
                id=activity.conversation.id,
                name=activity.conversation.name,
            ),
            text=text or "",
            text_format=text_format or None,
            locale=activity.locale,
        )

@JoshBello JoshBello added bug Indicates an unexpected problem or an unintended behavior. needs-triage The issue has just been created and it has not been reviewed by the team. labels Nov 23, 2020
@dmvtech dmvtech added Bot Services Required for internal Azure reporting. Do not delete. Do not change color. customer-reported Issue is created by anyone that is not a collaborator in the repository. labels Nov 23, 2020
@jwiley84 jwiley84 self-assigned this Nov 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. bug Indicates an unexpected problem or an unintended behavior. customer-reported Issue is created by anyone that is not a collaborator in the repository. needs-triage The issue has just been created and it has not been reviewed by the team.
Projects
None yet
Development

No branches or pull requests

3 participants