botframework-sdk icon indicating copy to clipboard operation
botframework-sdk copied to clipboard

Proactive Messages without Prior Context

Open Chocrates opened this issue 1 year ago • 3 comments

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 avatar May 28 '24 20:05 Chocrates

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 node_modules/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:[email protected]'                
  },                                                        
  channelId: '19:[email protected]',                                                         
  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                             
}                                                                                                             

Chocrates avatar May 29 '24 14:05 Chocrates

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

tracyboehrer avatar Jul 16 '24 14:07 tracyboehrer

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.

Chocrates avatar Jul 16 '24 14:07 Chocrates

@tracyboehrer I see you marked this as completed. I was curious if there was any proper solution that avoids the TypeError: claimsIdentity.getClaimValue error you mentioned.

I am working on sending a teams message and getting the same error but for sending to a proactive message to a specific userId. I am creating a new chat between my bot and user and then send the chatId to the azure bot service, but when using

    const conversationReference = {
      conversation: { id: chatId },
      serviceUrl: SERVICE_URL,
      claimsIdentity,
    };

    // Send the adaptive card
    await notificationApp.notification.adapter.continueConversationAsync(
      conversationReference,
      async (context) => {
        const card = {
          contentType: "application/vnd.microsoft.card.adaptive",
          content: adaptiveCard,
        };
        await context.sendActivity({ attachments: [card] });
      }
    );

I get the same error

lukefronadaptive avatar Dec 01 '24 01:12 lukefronadaptive

@lukefronadaptive You were able to find a way to solve the problem 'TypeError: claimsIdentity.getClaimValue'? I'm having the same error trying with "Bot Framework Emulator"

omaryupio avatar Jan 28 '25 15:01 omaryupio

@omaryupio Yes I resolved this. Most/all of the online examples are out of date, but I used this code which resolved my issue:

  try {
    const conversationParameters = {
      isGroup: false,
      channelData: { tenant: { id: tenantId } },
      bot: { id: process.env.BOT_ID },
      members: [{ id: userId }], // The AAD object ID for the user
      topicName: '1:1 Chat with Bot',
    };

    // Create a new conversation
    await notificationApp.adapter.createConversationAsync(
      process.env.BOT_ID, // botAppId
      'msteams', // channelId
       'https://smba.trafficmanager.net/teams/', // serviceUrl (region may vary)
      'https://api.botframework.com', // audience
      conversationParameters,
      async (turnContext) => {

        // Get the new chat ID
        const newChatId = turnContext.activity?.conversation?.id;

        // Send the adaptive card message from this same turnContext
        await turnContext.sendActivity({
          attachments: [
            {
              contentType: 'application/vnd.microsoft.card.adaptive',
              content: adaptiveCard,
            },
          ],
        });

        // Return success
        return res.status(200).json({
          success: true,
          message: 'Conversation created and notification sent',
          chatId: newChatId,
        });
      }
    );
  } catch (err) {
    console.error('Error creating conversation or sending notification:', err);
    return res.status(500).send({
      success: false,
      message: 'Failed to create new conversation or send notification',
      error: err.toString(),
    });
  }

lukefronadaptive avatar Jan 28 '25 15:01 lukefronadaptive