Отправка уведомлений в чате 1 к 1 пользователям в клиенте

#.net #botframework #microsoft-teams #microsoft-graph-sdks #microsoft-graph-teams

#.net #botframework #microsoft-команды #microsoft-graph-sdks #microsoft-graph-teams

Вопрос:

Я разрабатываю бот-уведомление для своей организации в Microsoft Teams, используя ASP.NET Core, Graph SDK (от Microsoft.Identity.Web.MicrosoftGraphBeta). У меня возникли трудности с разработкой простой системы уведомлений для конкретных пользователей в моем клиенте. Эти пользователи идентифицируются по адресу электронной почты. Я уже внедрил упреждающую установку приложения для пользователей и команд, выполнив:

Контроллер, который получает сообщение для отправки в MS Teams определенному пользователю

 [HttpPost]
[Route("notify-approver")]
public async Task<List<string>> NotifyApprover([FromBody] ApproverMessage approverMessage)
{
            var approvers = await _graphServiceClient.Users.Request().Filter($"mail eq '{approverMessage.Email}'").GetAsync();

            var targetApprover = approvers.CurrentPage.ToList().FirstOrDefault();

            var appInstallation= await _graphServiceClient.InstallIfNotAlreadyForUser(targetApprover.Id, _configuration.GetTeamsAppId());


            // PER IL MOMENTO SEMBRA BUG
            var chatId = await _graphServiceClient.GetChatThreadId(targetApprover.Id, appInstallation.Id)

        }
 

Установка работает нормально и возвращает UserScopeTeamsAppInstallation.
Это функция, которую я создал для упреждающей установки:

 public static async Task<UserScopeTeamsAppInstallation> InstallIfNotAlreadyForUser(this IGraphServiceClient graphServiceClient, string userId, string teamsAppId)
        {
            var appsCollectionPage = await graphServiceClient.Users[userId].Teamwork.InstalledApps.Request().Expand("teamsAppDefinition")
                .GetAsync();
            var appInstallation = appsCollectionPage.CurrentPage.ToList()
                .Find(a => a.TeamsAppDefinition.TeamsAppId.Equals(teamsAppId));

            if (appInstallation != null) return appInstallation;
            var userScopeTeamsAppInstallation = new UserScopeTeamsAppInstallation()
            {
                AdditionalData = new Dictionary<string, object>
                {
                    {"teamsApp@odata.bind", $"https://graph.microsoft.com/beta/appCatalogs/teamsApps/{teamsAppId}"}
                }
            };
            var freshInstallation = await graphServiceClient.Users[userId].Teamwork.InstalledApps.Request().AddAsync(userScopeTeamsAppInstallation);
            return freshInstallation;

        }
 

До сих пор все было в порядке, но плохая часть заключается в пробной отправке упреждающего сообщения; как предложено в документации, я продолжаю получать этот идентификатор чата, который должен быть полезен. Самое смешное, что если я использую Graph Client версии v1.0, я получаю внутреннюю ошибку сервера при получении этого объекта чата ниже, затем я переключился на бета-клиент, и это … вроде работает.

 public static async Task<string> GetChatThreadId(this IGraphServiceClient graphServiceClient, string userId, string teamsAppIdInstallation)
        {
            // forse ci vuole AppInstallationId anziché il semplice teamsAppId della app

            //var chat = await graphServiceClient.Users[userId].Teamwork.InstalledApps[teamsAppIdInstallation].Chat.Request().GetAsync();

            /*
             * Perché dá internal server error?? Possibile bug della API di graph
             * perché viceversa da graph explorer funziona e restituisce l'id della chat
             *
             * Per adesso sono costretto a salvarmi in un database tutte le conversation references.
             */

            var chat =  graphServiceClient.Users[userId].Teamwork.InstalledApps[teamsAppIdInstallation].Chat.Request().GetAsync();


            return chat.Id.ToString();
            //return "CIAONE";
        }
 

Объект чата, полученный при извлечении в функции GetChatThreadId
С этого момента я все больше и больше запутываюсь в количестве разбросанной информации.
Я видел, что обычно это должно быть:

  • При обратном вызове бота OnConversationUpdateActivityAsync ссылка ConversationReference должна быть сохранена, поскольку она запускается при установке бота. Но тогда этот объект хранится в ConcurrentDictionary, и его время жизни ограничено временем выполнения. В моем случае я должен сохранить его, но я не могу найти, какие свойства сохранить и как воссоздать объект ConversationReference, который в примерах всегда сохраняется как есть, но без примера с сохранением.
  • Учитывая вышеуказанную проблему, я не могу использовать на адаптере бота метод ContinueConversationAsync.
  • В любом случае, если мы посмотрим на документацию, даже если в ней явно указано, чтобы получить этот идентификатор чата, он выполняет операцию с помощью кэшированных объектов ConversationReference.

Как я должен делать такую «простую» вещь, как отправка простого сообщения человеку, в то время как это выглядит так запутанно?

Ответ №1:

Чтобы сохранить свойства ссылки на диалог, проверьте концепцию уровня хранения в документах состояния бота

Вы можете продолжить и попробовать, поместив этот пример кода в файл startup.cs:

 services.AddSingleton<IStorage>(new AzureBlobStorage(Configuration["_connectionstring"], Configuration["BlobName"]));
services.AddSingleton<UserState>();
services.AddSingleton<ConversationState>();
services.AddSingleton<BotStateService>(); 
 

А также, пожалуйста, ознакомьтесь с примером кода proactive-messages.

Комментарии:

1. спасибо за ответ. В итоге я нашел «простое» решение без особых проблем, сохранив каждый объект ConversationReference в локальной базе данных Sqlite: я сериализую его в JSON, а затем сохраняю как строку json, и когда я извлекаю его, я десериализую его из строки json и использую, как описано в документации по упреждающим сообщениям

Ответ №2:

Простым решением, которое я нашел, было сохранение объекта ConversationReference в обратном вызове OnTeamsMembersAddedAsync, когда бот установлен для пользователя, путем сериализации его в json и сохранения в базе данных Sqlite. Для его извлечения я десериализую его и использую