Search code examples
microsoft-graph-apibotframeworkmicrosoft-teamsteams-toolkit

MS Teams Bot - Create a Conversation with User


I am creating an azure function app that should create a conversation with every user in Entra Id- I run into an error message: Error creating conversation2: Error: Error creating conversation: Unauthorized, {"message":"Authorization has been denied for this request."}. How can I create the conversation for each user when the bot is only installed in the group channel. Let me know if more information is needed for this question.

I use a graph token generated with the CCA - code below


const msalConfig = {
  auth: {
    clientId: process.env.MICROSOFT_APP_ID,
    clientSecret: process.env.MICROSOFT_APP_PASSWORD,
    authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`,
  }
};

const cca = new ConfidentialClientApplication(msalConfig);

export async function getGraphToken(): Promise<string> {
  try {
    const authResult = await cca.acquireTokenByClientCredential({
      scopes: ['https://graph.microsoft.com/.default']
    });
    console.log('token', authResult.accessToken)
    return authResult.accessToken;
  } catch (error) {
    console.error("Error acquiring Graph token:", error);
    throw new Error("Failed to acquire Graph token");
  }
}```

i use this to get every user in Entra and then for each user, i try to create and save a conversation

```import { app, InvocationContext, Timer } from "@azure/functions";
import { saveConversationReference, getAllUsers, CreateConversationForMSTeamsUser, addUserRefToStorage } from "../extra/conversationHandlers";

export async function createConversationInStorage2(myTimer: Timer, context: InvocationContext): Promise<void> {
    context.log('Timer function processed request.');

    try {
        const users = await getAllUsers();
        context.log(`Fetched ${users.length} users from Azure AD`);
        for (const user of users) {
            if (user && user.id) {
                context.log(`Processing user: ${user.id}`);
                const res = await CreateConversationForMSTeamsUser(user.id, user.displayName, user.userPrincipalName);
                if (res) {
                    await addUserRefToStorage(user.id, res);
                } else {
                    context.log(`Failed to create conversation for user3: ${user.id}`);
                }
            }
        }
    } catch (error) {
        context.log('Error retrieving users from Azure AD:', error);
    }
}

app.timer('createConversationInStorage2', {
    schedule: '0 */1 * * * *',
    handler: createConversationInStorage2
});```


- handlers below 

```import storage from './storageBlob';
import { getBotFrameworkToken, getGraphToken } from './msal';

export async function CreateConversationForMSTeamsUser(userId: string, userDisplayName: string, userPrincipalName: string) {
  try {
    const tenantId = process.env.TENANT_ID;
    // const serviceUrl = 'https://smba.trafficmanager.net/amer/v3/conversations';
    const serviceUrl = 'https://smba.trafficmanager.net/teams/v3/conversations'
    const token = await getGraphToken();
    // console.log('everything', userId, userDisplayName, userPrincipalName, tenantId, serviceUrl)
    // console.log('emicrosoft details', 'appid', process.env.MICROSOFT_APP_ID, 'appsecret', process.env.MICROSOFT_APP_PASSWORD, 'tenantid', process.env.TENANT_ID)
    const conversationParams = {
      members: [
        {
          id: userId,
          displayName: userDisplayName,
          userPrincipalName: userPrincipalName
        },
      ],
      isGroup: false,
      bot: {
        id: 'id here',
        displayName: 'app name here'
      }
    };

    console.log('conversationParams', conversationParams)

    // const conversationParams = {
    //   activity: 'hi',
    //   bot: [
    //     {
    //       aadObjectId: process.env.MICROSOFT_APP_ID,
    //       id: 'id here',
    //       name: 'app name here',
    //       role: 'bot'
    //     }
    //   ],
    //   channelData: { tenant: { id: tenantId } },
    //   isGroup: false,
    //   members: [
    //     {
    //       aadObjectId: userId,
    //       id: `29:${userId}`,
    //       name: userDisplayName,
    //       role: 'user',
    //       userPrincipalName: userPrincipalName
    //     }
    //   ],
    //   tenantId: tenantId,
    //   topicName: 'General'
    // }

    const conversationResponse = await fetch(
      serviceUrl,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(conversationParams),
      }
    );

    console.log('conversationResponse', conversationResponse)

    if (!conversationResponse.ok) {
      const errorText = await conversationResponse.text();
      throw new Error(`Error creating conversation: ${conversationResponse.statusText}, ${errorText}`);
    }

    const conversation = await conversationResponse.json();
    console.log('conversation', conversation)
    return conversation;
  } catch (error) {
    console.error('Error creating conversation2:', error);
    return null;
  }
}

export async function saveConversationReference(userId: string, conversationId: string) {
  const reference = {
    user: { id: userId },
    conversation: { id: conversationId }
  };
  try {
    const res = await storage.add(userId, reference, { overwrite: true });
    console.log(`Conversation reference saved for user ${userId}:`, res);
  } catch (error) {
    console.error(`Error saving conversation reference for user ${userId}:`, error);
  }
}

export async function getAllUsers(): Promise<any[]> {
  const token = await getGraphToken();
  const usersResponse = await fetch("https://graph.microsoft.com/v1.0/users", {
    headers: {
      Authorization: `Bearer ${token}`
    },
  });

  if (!usersResponse.ok) {
    throw new Error(`Error fetching users: ${usersResponse.statusText}`);
  }

  const usersJson = await usersResponse.json();

  if (!usersJson.value || !Array.isArray(usersJson.value)) {
    throw new TypeError('Expected an array of users in the response');
  }

  return usersJson.value;
}


export async function addUserRefToStorage(userId: string, conversationRef: any) {
  try {
    if (!conversationRef) {
      throw new Error('Invalid conversation reference');
    }
    const res = await storage.add(userId, conversationRef, { overwrite: true });
    console.log(`User ID saved for user ${userId}:`, res);
  } catch (error) {
    console.error(`Error saving user conversation reference for ${userId}:`, error);
  }
}
  • i also had a theory there might be some other type of token that im supposed to use- apparently i can pass it the botframwork scope and get a token. if this is the endpoint im supposed to use, how do i give it permission to create chats and readwrite permission? (this is already done for the graph token)
  try {
    const authResult = await cca.acquireTokenByClientCredential({
      scopes: ['https://api.botframework.com/.default']
      // scopes: ['https://smba.trafficmanager.net/.default']
    });
    console.log('authResult', authResult.accessToken);
    return authResult.accessToken;
  } catch (error) {
    console.error("Error acquiring Bot Framework token:", error);
    throw new Error("Failed to acquire Bot Framework token");
  }
}

Solution

  • It's important to remember that Bot Framework can be used in many contexts, Teams is just one of those. As such, while the concept of "creating" a conversation exists in the Bot Framework, it's not valid within Teams - there is only ever a single "conversation", and you are basically "continuing" the conversation. It's also not possible to be the first to "initiate" the conversation - the user needs to do that, and it's done by installing the app that contains the bot (which will send an installation event to your bot that it should reply to with a "welcome" message).

    As a result, what you're looking to do, once the app/bot is installed and you want to send messages periodically in the future, is called "Proactive messaging". You can read more about it from Microsoft here as well as a more detailed answer I've posted before, with sample code and video links, at Sending proactive messages from an outside process to organizational users via Teams chat bot .