コード例 #1
0
        public async Task Run(
#pragma warning disable CA1801 // Remove unused parameter
            [TimerTrigger("0 */1 * * * *")] TimerInfo timerInfo)
#pragma warning restore CA1801 // Remove unused parameter
        {
            if (_appSettings is null ||
                _appSettings.Value is null ||
                _appSettings.Value.EmailToSmsSettings is null ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.ResellerApiKey) ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.ResellerApiSecret) ||
                string.IsNullOrEmpty(_appSettings.Value.EmailToSmsSettings.MailboxAddress))
            {
                _logger.LogWarning("Missing Email to SMS configuration. Aborting!");
                return;
            }

            // TODO Move GraphServiceClient and auth to DI
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                                                                           .Create(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiClientId)
                                                                           .WithTenantId(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiTenantID)
                                                                           .WithClientSecret(_appSettings.Value.EmailToSmsSettings.MicrosoftGraphApiSecret)
                                                                           .Build();

            var graphServiceClient = new GraphServiceClient(new ClientCredentialProvider(confidentialClientApplication));

            // TODO, make this better or move to config.
            // Hard coding OWNit conveyancing for now.
            var clientIds = new Dictionary <string, int>();

            clientIds.Add("b6rtmjuc", 87627);

            var messageExceptions = new List <Exception>();

            ISMSService smsService = new BurstSMSService(
                _appSettings.Value.EmailToSmsSettings.ResellerApiKey,
                _appSettings.Value.EmailToSmsSettings.ResellerApiSecret);

            var mailboxEmailAddress = _appSettings.Value.EmailToSmsSettings.MailboxAddress;

            var currentMessageRequest = graphServiceClient.Users[mailboxEmailAddress]
                                        .MailFolders.Inbox
                                        .Messages
                                        .Request()
                                        .Select(m => (new
            {
                m.Id,
                m.From,
                m.Subject,
                m.Body,
                m.InternetMessageHeaders,
                m.ParentFolderId,
                m.Categories
            }));

            // Get IDs of standard folders to be able to move messages once they're processed
            var inboxFolder           = await graphServiceClient.Users[mailboxEmailAddress].MailFolders.Inbox.Request().GetAsync();
            var invalidMessagesFolder = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder("Inbox", _invalidMessagesFolderName);
            var clientsFolder         = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder("Inbox", _clientsFolderName);
            var clientFolderIds       = new Dictionary <string, string>();

            // Loop through pages
            do
            {
                var currentMessages = await currentMessageRequest.GetAsync();

                // Loop through messages in current page
                foreach (var message in currentMessages)
                {
                    var parsedBodyText = string.Empty;

                    try
                    {
                        var messageBodyHtmlDoc = new HtmlAgilityPack.HtmlDocument();
                        messageBodyHtmlDoc.LoadHtml(message.Body.Content);

                        // - Using InnerText strips HTML.
                        // - Then we replace Windows CRLF's in case there are any. We must strip these first because if we
                        //   stripped LF first then we'd have a bunch of CR's floating around all alone.
                        // - Then strip Unix LF's, in case there are any.
                        parsedBodyText = HttpUtility.HtmlDecode(messageBodyHtmlDoc.DocumentNode.InnerText)
                                         .Replace("\r\n", "", StringComparison.Ordinal)
                                         .Replace("\n", "", StringComparison.Ordinal);

                        var smsEmailJson = JsonConvert.DeserializeObject <SmsEmailJson>(parsedBodyText);

                        // Check for required values
                        if (!smsEmailJson.IsValid(out List <string> missingFields))
                        {
                            // Create a support ticket with the details.
                            var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                            {
                                FromEmail       = GetBestEmail(smsEmailJson),
                                TicketPriority  = TicketPriority.Low,
                                Subject         = GenerateTicketSubject(smsEmailJson),
                                DescriptionHtml = GenerateMessageMissingFields(smsEmailJson, missingFields)
                            });

                            // Store the category for a bad response from the gateway
                            await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, new[] { $"Ticket: {ticketResponse.Id}", EmailCategories.MissingRequiredData });

                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(invalidMessagesFolder.Id).Request().PostAsync();
                        }
                        else
                        {
                            // Data appears valid, ensure folder exists for client, checking cached IDs first
                            if (smsEmailJson.SmsGo.Value == true)
                            {
                                // Send SMS. Will throw if there's a problem.
                                var clientId = clientIds[smsEmailJson.SmsKey];

                                var cleanMobileNumber = SMSHelper.CleanMobileNumber(smsEmailJson.MobileNumber);

                                if (_appSettings.Value.EmailToSmsSettings.SkipSendingSms)
                                {
                                    _logger.LogWarning(
                                        "Skipping SMS Send: Client ID: {clientId}, Mobile Number: {mobileNumber}, Message: '{message}', Send At: {sendAt}, Replies to Email: {repliesToEmail}",
                                        clientId,
                                        cleanMobileNumber,
                                        smsEmailJson.Message,
                                        smsEmailJson.SendAt,
                                        smsEmailJson.RepliesToEmail);

                                    await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsSkipped);
                                }
                                else
                                {
                                    var sendAt = SMSHelper.LocalisedSendAt(smsEmailJson.SendAt, smsEmailJson.SendAtTimeZoneID, _logger);

                                    if (sendAt != smsEmailJson.SendAt)
                                    {
                                        _logger.LogInformation(
                                            "SendAt was translated from '{SendAt}' to '{ParsedSendAt}' based on supplied Time Zone ID '{TimeZoneID}'.",
                                            smsEmailJson.SendAt,
                                            sendAt,
                                            smsEmailJson.SendAtTimeZoneID);
                                    }

                                    var sendSmsResponse = await smsService.SendSms(clientId, cleanMobileNumber, smsEmailJson.Message, sendAt, smsEmailJson.RepliesToEmail);

                                    if (sendSmsResponse.Error is null)
                                    {
                                        // Create a support ticket with the details.
                                        var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                        {
                                            FromEmail       = GetBestEmail(smsEmailJson),
                                            TicketPriority  = TicketPriority.Low,
                                            Subject         = GenerateTicketSubject(smsEmailJson),
                                            DescriptionHtml = GenerateMessageUnknownGatewayResponse(smsEmailJson)
                                        });

                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.UnknownError);
                                    }
                                    else if (sendSmsResponse.Error.Code == "SUCCESS")
                                    {
                                        _logger.LogInformation(
                                            "SMS Sent: Cost: {cost}, Sms Message ID: {messageId}, Code: {code}, Descripton: {description}, Send At: {sendAt}",
                                            sendSmsResponse.Cost,
                                            sendSmsResponse.MessageId,
                                            sendSmsResponse.Error?.Code,
                                            sendSmsResponse.Error?.Description,
                                            sendSmsResponse.SendAt);

                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsSent);
                                    }
                                    else
                                    {
                                        // Create a support ticket with the details.
                                        var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                        {
                                            FromEmail       = GetBestEmail(smsEmailJson),
                                            TicketPriority  = TicketPriority.Low,
                                            Subject         = GenerateTicketSubject(smsEmailJson),
                                            DescriptionHtml = GenerateMessageGatewayError(message, smsEmailJson, sendSmsResponse)
                                        });

                                        // Store the category for a bad response from the gateway
                                        await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.GatewayBadRequest);

                                        _logger.LogError(
                                            "SMS Gateway returned error response. Code: '{code}', Description: '{description}'.",
                                            sendSmsResponse.Error.Code,
                                            sendSmsResponse.Error.Description);
                                    }
                                }
                            }
                            else
                            {
                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.SmsGoFalse);
                            }

                            // Finally, move to client folder
                            string currentClientFolderId;
                            if (clientFolderIds.ContainsKey(smsEmailJson.OrgKey))
                            {
                                currentClientFolderId = clientFolderIds[smsEmailJson.OrgKey];
                            }
                            else
                            {
                                var currentClientFolder = await graphServiceClient.Users[mailboxEmailAddress].EnsureFolder(clientsFolder, smsEmailJson.OrgKey);
                                currentClientFolderId = currentClientFolder.Id;
                            }

                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(currentClientFolderId).Request().PostAsync();
                        }
                    }
                    catch (Exception ex)
                    {
                        messageExceptions.Add(ex);

                        // Just in case something goes wrong, we don't want a failure during cleanup to prevent processing future messages
                        try
                        {
                            if (ex is JsonReaderException jex)
                            {
                                // Create a support ticket with the details.
                                var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                {
                                    FromEmail       = _fallbackEmailFrom,
                                    TicketPriority  = TicketPriority.Low,
                                    Subject         = $"SMS Failure - couldn't parse JSON",
                                    DescriptionHtml = GenerateMessageJsonParseError(parsedBodyText, jex)
                                });

                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, $"Ticket: {ticketResponse.Id}", EmailCategories.InvalidJson);
                            }
                            else
                            {
                                var ticketResponse = await _mediator.Send(new CreateTicketCommand()
                                {
                                    FromEmail       = _fallbackEmailFrom,
                                    TicketPriority  = TicketPriority.Low,
                                    Subject         = $"SMS Failure - Unknown error",
                                    DescriptionHtml = GenerateMessageUnknownException(message, ex)
                                });

                                await graphServiceClient.AddMessageCategoriesAsync(mailboxEmailAddress, message, EmailCategories.UnknownError);
                            }

                            // Move to generic invalid message folder
                            await graphServiceClient.Users[mailboxEmailAddress].Messages[message.Id].Move(invalidMessagesFolder.Id).Request().PostAsync();
                        }
                        catch (Exception cleanupEx)
                        {
                            _logger.LogError(cleanupEx, "Exception encountered during message cleanup!");
                            messageExceptions.Add(cleanupEx);
                        }
                    }
                }

                currentMessageRequest = currentMessages.NextPageRequest;
            } while (currentMessageRequest != null);

            if (messageExceptions.Count > 0)
            {
                throw new AggregateException("Processing of one or more messages has failed", messageExceptions);
            }
        }