microsoft / botframework-sdk

Bot Framework provides the most comprehensive experience for building conversation applications.
MIT License
7.5k stars 2.45k forks source link

Proactive Messages without Prior Context #6650

Closed Chocrates closed 1 month ago

Chocrates commented 5 months ago

I know this is not for asking for help, but I went the Stack Overflow route and I did not get any (correct) answers, or I am just not smart enough to put the pieces together.

I even went as far as to try to do this with the Graph API but when I got everything together to sent the message it gave me an error that it is explicitly not allowed to post messages as the bot through that API and to use the Bot Framework SDK

Scenario

I need a service to listen for a webhook from a 3rd party and then use data from that webhook to message a information to a Team Channel in Microsoft Teams.
The message needs to come from the Bot and not a User. The message cannot be tied to any previous conversation because ideally this should create a new channel and invite users that are needed to address whatever the event from the webhook was.

This service will eventually do normal user initiated ChatOps stuff, but this first iteration seems to be very non standard.

Sample project can be found here https://github.com/Chocrates/chatops-sample

Webhook Listener

server.post('/api/proactiveMessages', async (req, res) => {
    // Route received a request to adapter for processing
    console.log('Inside proactive"')
    await myBot.teamsCreateConversation(adapter);
    res.send(200)
});

Broken Proactive Message Code

    async teamsCreateConversation(adapter) {
        const channelId = 'snip'
        const teamId = 'snip'

        const reference = TurnContext.getConversationReference({
            bot: {
                id: process.env.MicrosoftAppId,
                name: 'Your Bot'
            },
            channelId: channelId,
            conversation: {
                isGroup: true,
                conversationType: 'channel',
                id: `${teamId};messageid=${channelId}`
            },
            serviceUrl: 'https://smba.trafficmanager.net/amer/',
            user: {
                id: 'user-id-placeholder',
                name: 'User'
            }
        });

        await adapter.continueConversationAsync(reference, async (turnContext) => {
            await turnContext.sendActivity('Hello, this is a proactive message from the bot!');
        });

        console.log('After sending the message')
    }

The latest error I have been getting is that TypeError: claimsIdentity.getClaimValue is not a function, but basically every change I try gives me new and different errors, so something is fundamentally wrong with my understanding of the SDK.

Does anyone have any working sample code I can look at? So far everything I have found about sending proactive messages needs a conversation reference which I will not have in this scenario.

Chocrates commented 5 months ago

Pulling the proactive message from this sample like so

const reference = TurnContext.getConversationReference({
            bot: {
                id: '28:' + process.env.MicrosoftAppId,
                name: 'Your Bot'
            },
            channelId: channelId,
            conversation: {
                isGroup: true,
                conversationType: 'channel',
                id: `${teamId};messageid=${channelId}`
            },
            serviceUrl: 'https://smba.trafficmanager.net/amer/',
            user: {
                id: 'user-id-placeholder',
                name: 'User'
            }
        });

        await adapter.continueConversationAsync(reference, async (turnContext) => {
            await turnContext.sendActivity('Hello, this is a proactive message from the bot!');
        });

Yields the claim identity error above.

Adding some logging to the library itself, to this function in _nodemodules/botframework-connector/lib/auth/parameterizedBotFrameworkAuthentication.js

function getAppId(claimsIdentity) {
    const util = require('util')
    console.log(` Inside CLaims stuff: ${ util.inspect( claimsIdentity)}`)
    var _a, _b;
    // For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For
    // unauthenticated requests we have anonymous claimsIdentity provided auth is disabled.
    // For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
    return ((_b = (_a = claimsIdentity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AudienceClaim)) !== null && _a !== void 0 ? _a : claimsIdentity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AppIdClaim)) !== null && _b !== void 0 ? _b : undefined);
}

Nets this result. Clearly I am authing somehow incorrect but some of the data in there does look familiar.

{                                     
  activityId: undefined,                                    
  user: undefined,                                          
  bot: undefined,                                           
  conversation: {                                           
    isGroup: true,                                          
    conversationType: 'channel',
    id: '89e34e16-1ef0-41a3-9186-ac19de141260;messageid=19:96c3e7b1129f4f39ac7bac5c3969c334@thread.tacv2'                
  },                                                        
  channelId: '19:96c3e7b1129f4f39ac7bac5c3969c334@thread.tacv2',                                                         
  locale: undefined,                                        
  serviceUrl: 'https://smba.trafficmanager.net/amer/'
}

Printing the claim when doing a user initiated conversation nets this object.

ClaimsIdentity {                                                                              
  claims: [                                            
    {                                                  
      type: 'serviceurl',                                                                                     
      value: 'https://smba.trafficmanager.net/amer/'                                                          
    },                                                                                                        
    { type: 'nbf', value: 1716993450 },                                                                       
    { type: 'exp', value: 1716997050 },                                                                       
    { type: 'iss', value: 'https://api.botframework.com' },                                                   
    { type: 'aud', value: 'bfdecf05-af65-4061-b960-29ded0e0e4ac' }                                            
  ],                                                                                                          
  authenticationType: true                             
}                                                                                                             
tracyboehrer commented 4 months ago

Have you posted this to the Teams Samples repo? This is more of a Teams question since this is unique to the Teams channel.

Chocrates commented 4 months ago

Good idea @tracyboehrer. I never got this to work.

To get proactive messaging working I ended up installing the app to a specific team, saving that team reference (A webhook is fired off to your bot and I saved the raw data to a json object). Once you have that object you can use the graph api to create a new channel in the team and then update the channel id on the initial reference and send a message to that new channel.

I kind of hate it, it seems hacky, but it is working.