Beispiel #1
0
        private async Task RunChartHandlerAsync(Restaurant restaurant, StatsBotScheduler statScheduler, CancellationToken stoppingToken)
        {
            if (!TimeSpan.TryParseExact(statScheduler.SendAt, "c", CultureExtensions.DefaultCulture, TimeSpanStyles.None,
                                        out var sendAt))
            {
                _logger.LogError("SendAt parameter has incorrect format");
                return;
            }

            if (sendAt < TimeSpan.Zero || sendAt > new TimeSpan(23, 59, 59))
            {
                _logger.LogError("SendAt is lower than 00:00:00 or greater than 23:59:59");
                return;
            }

            if (statScheduler.TakeDays < 1)
            {
                _logger.LogError("TakeData is lower than 1");
                return;
            }

            var takeDays = new TimeSpan(statScheduler.TakeDays, 0, 0, 0);

            _logger.LogDebug("TakeDays is {0}", statScheduler.TakeDays);

            var lastStat = await _context.SentStats
                           .Find(ss => ss.StatId == statScheduler.Id)
                           .SortByDescending(ss => ss.SentDate)
                           .Project(ss => ss.SentDate)
                           .FirstOrDefaultAsync(stoppingToken).ConfigureAwait(false);

            _logger.LogDebug("LastStat is {0}", lastStat);

            if (lastStat == default)
            {
                _logger.LogInformation("lastStat is default, processing expected value");

                var now = _cultureService.NowFor(restaurant);
                _logger.LogDebug("now is {0}", now);

                lastStat = now.Date.Subtract(takeDays);
                _logger.LogDebug("lastStat, subtracted from now, is {0}", lastStat);

                if (now.TimeOfDay > sendAt)
                {
                    _logger.LogInformation("now.TimeOfDay > sendAt", now.TimeOfDay, sendAt);
                    lastStat = lastStat.AddDays(1d);
                    _logger.LogDebug("lastStat incremented by 1 day: {0}", lastStat);
                }

                if (statScheduler.StartFromDayOfWeek.HasValue && lastStat.DayOfWeek != statScheduler.StartFromDayOfWeek.Value)
                {
                    _logger.LogInformation("StartFromDayOfWeek has value. {0} != {1}",
                                           lastStat.DayOfWeek, statScheduler.StartFromDayOfWeek.Value);

                    var time1Day = TimeSpan.FromDays(1d);
                    while (lastStat.DayOfWeek != statScheduler.StartFromDayOfWeek.Value)
                    {
                        lastStat = lastStat.Add(time1Day);
                    }

                    _logger.LogDebug("lastStat updated to be {0}. Now its value is {1}",
                                     statScheduler.StartFromDayOfWeek.Value, lastStat);
                }
            }

            TimeSpan GetDelayTime()
            {
                var expectedDate  = lastStat.Add(takeDays + sendAt);
                var delayTimeTemp = expectedDate - _cultureService.NowFor(restaurant);

                _logger.LogDebug("Delay time is {0}", delayTimeTemp);

                return(delayTimeTemp);
            }

            await Task.Delay(GetDelayTime(), stoppingToken).ConfigureAwait(false);

            var culture = _cultureService.CultureFor(restaurant);

            while (!stoppingToken.IsCancellationRequested)
            {
                var now            = _cultureService.NowFor(restaurant);
                var statsForPeriod = await _context.SentForms
                                     .Find(sf => sf.Date >= lastStat && sf.Date <= now.Date && sf.RestaurantId == restaurant.ChatId)
                                     .ToListAsync(stoppingToken).ConfigureAwait(false);

                var pieChartDictionary = new Dictionary <string, int>();
                foreach (var stat in statsForPeriod)
                {
                    var isFirstDay = stat.Date == now.Date;
                    var isLastDay  = stat.Date == lastStat;

                    foreach (var statItem in stat.Items)
                    {
                        if (isFirstDay && statItem.SentTime < sendAt || isLastDay && statItem.SentTime > now.TimeOfDay)
                        {
                            continue;
                        }

                        pieChartDictionary.TryGetValue(statItem.EmployeeName, out var employeeWeight);
                        pieChartDictionary[statItem.EmployeeName] = employeeWeight + 1;
                    }
                }

                var pieChartItems = new List <PieChartItem>();
                foreach (var(employeeName, weight) in pieChartDictionary)
                {
                    pieChartItems.Add(new PieChartItem(weight, employeeName));
                }

                await using (var ms = new MemoryStream())
                {
                    await _chartClient.LoadDoughnutPieChartAsync(ms, pieChartItems).ConfigureAwait(false);

                    var model = _cultureService.ModelFor(restaurant);
                    await _client.SendPhotoAsync(restaurant.ChatId, ms,
                                                 string.Format(culture, model.StatsForPeriod, (lastStat + sendAt).ToString("G", culture),
                                                               now.ToString("G", culture)), ParseMode.Html, cancellationToken : stoppingToken).ConfigureAwait(false);
                }

                lastStat = now.Date;

                await Task.Delay(GetDelayTime(), stoppingToken).ConfigureAwait(false);
            }
        }
Beispiel #2
0
        private async Task GetNotifierTask(CancellationToken cancellationToken)
        {
            var notSentReviews = await _context.Reviews
                                 .Find(r => r.NeedToShow)
                                 .SortByDescending(r => r.Id)
                                 .ToListAsync(cancellationToken).ConfigureAwait(false);

            if (notSentReviews.Count == 0)
            {
                if (_env.IsDevelopment())
                {
                    _logger.LogInformation("Count of notSentReviews is 0.");
                }

                return;
            }

            cancellationToken.ThrowIfCancellationRequested();

            foreach (var restaurantGroup in notSentReviews.GroupBy(r => r.RestaurantName))
            {
                var restaurant = _data.Restaurants.Find(r => r.Name == restaurantGroup.Key);
                if (restaurant == default)
                {
                    _logger.LogInformation("Restaurant named '{0}' was not found.", restaurantGroup.Key);
                    continue;
                }

                foreach (var notSentReviewGroup in restaurantGroup.GroupBy(r => r.Resource))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (_env.IsDevelopment())
                    {
                        await _client.SendTextMessageAsync(restaurant.ChatId,
                                                           $"Resource: {notSentReviewGroup.Key}\nCount of reviews: {notSentReviewGroup.Count()}").ConfigureAwait(false);
                    }

                    foreach (var notSentReview in notSentReviewGroup.Take(5))
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        var model   = _cultureService.ModelFor(restaurant);
                        var culture = _cultureService.CultureFor(restaurant);

                        var buttons = new List <InlineKeyboardButton[]>();
                        if ((notSentReview.Comments?.Count ?? 0) > 0)
                        {
                            buttons.Add(new[]
                            {
                                new InlineKeyboardButton
                                {
                                    Text         = model.ViewFeedback,
                                    CallbackData = $"comments~{notSentReview.Id}"
                                }
                            });
                        }
                        if (!notSentReview.IsReadOnly && notSentReview.ReplyLink != null &&
                            notSentReview.Resource != "google")
                        {
                            buttons.Add(new[]
                            {
                                new InlineKeyboardButton {
                                    Text = model.OpenReview, Url = notSentReview.ReplyLink
                                }
                            });
                        }

                        var chatId = restaurant.ChatId;

                        Message sentMessage;
                        if (notSentReview.Photos == null || notSentReview.Photos.Count is 0 or > 1)
                        {
                            sentMessage = await _client.SendTextMessageAsync(chatId, notSentReview.ToString(culture, model,
                                                                                                            _data.ReviewBot.MaxValuesOfRating.TryGetValue(notSentReview.Resource,
                                                                                                                                                          out var maxValueOfRating)
                                        ? maxValueOfRating
                                        : -1,
                                                                                                            _data.ReviewBot.PreferAvatarOverProfileLinkFor.Contains(notSentReview.Resource)),
                                                                             ParseMode.Html, disableWebPagePreview : notSentReview.Resource == "instagram",
                                                                             cancellationToken : cancellationToken, replyMarkup : buttons.Count > 0
                                                                             ?new InlineKeyboardMarkup(buttons)
                                                                             : null).ConfigureAwait(false);

                            if (notSentReview.Photos is { Count: > 1 })
        internal async Task ExecuteAsync(Restaurant restaurant, CancellationToken stoppingToken,
                                         DateTime?requestedDate = null, long userId = 0L)
        {
            var forDate   = requestedDate ?? _cultureService.NowFor(restaurant).AddDays(1d);
            var culture   = _cultureService.CultureFor(restaurant);
            var monthName = culture.DateTimeFormat.GetMonthName(forDate.Month);
            var model     = _cultureService.ModelFor(restaurant);

            var range = $"{monthName} {forDate:MM/yyyy}!$A$1:$YY";

            if (_env.IsDevelopment())
            {
                await _client.SendTextMessageAsync(restaurant.ChatId, $"Range is {range}\n\nRequested date is {forDate}",
                                                   cancellationToken : stoppingToken).ConfigureAwait(false);
            }

            var response =
                await _googleSheetsService.GetValueRangeAsync(restaurant.DistributionBot.SpreadsheetId, range,
                                                              stoppingToken).ConfigureAwait(false);

            if (response?.Values == null || response.Values.Count == 0)
            {
                try
                {
                    if (userId > 0)
                    {
                        await _client.SendTextMessageAsync(userId, model.TimeBoardIsNotAvailableForThisMonth,
                                                           cancellationToken : stoppingToken).ConfigureAwait(false);
                    }
                }
                catch
                {
                    await _client.SendTextMessageAsync(-1001463899405L,
                                                       $"Пользователь [{userId}](tg://user?id={userId}) не начал чат с ботом или заблокировал бот.",
                                                       cancellationToken : stoppingToken).ConfigureAwait(false);
                }

                await _client.SendTextMessageAsync(-1001463899405L, "Гугл таблица с расписанием недоступна.",
                                                   cancellationToken : stoppingToken).ConfigureAwait(false);

                return;
            }

            var day      = forDate.Day;
            var dateText = forDate.ToString("D", culture);
            var privates = new Dictionary <int, string>();
            var users    = new List <(int userId, string name, string time)>();

            foreach (var row in response.Values)
            {
                if (row.Count < day + 3 ||
                    !int.TryParse(row[1].ToString().AsSpan(), out var rowUserId) &&
                    row[2].ToString().AsSpan().IndexOf('+') != 0 ||
                    privates.ContainsKey(rowUserId))
                {
                    continue;
                }

                var dayText = row[day + 2].ToString();
                if (string.IsNullOrWhiteSpace(dayText))
                {
                    continue;
                }

                {
                    var place = dayText[0];
                    if (char.IsLetter(place))
                    {
                        if (restaurant.PlaceId != place || dayText.Length == 1)
                        {
                            continue;
                        }

                        dayText = new string(dayText.AsSpan(1).TrimStart());
                    }
                }

                var name = row[0].ToString();

                users.Add((rowUserId, name, dayText));

                if (rowUserId == 0 || userId > 0 && rowUserId != userId)
                {
                    continue;
                }

                privates[rowUserId] = string.Format(culture, model.YouWorkAt, name, dateText,
                                                    new string($"{dayText} {restaurant.PlaceInfo}".AsSpan().TrimEnd()));
            }

            if (users.Count == 0)
            {
                return;
            }

            foreach (var(privateUserId, privateText) in privates)
            {
                var privateTextBuilder = new StringBuilder();
                privateTextBuilder.Append(privateText);
                privateTextBuilder.Append("\n\n");
                privateTextBuilder.AppendFormat(culture, model.WhoWorksWithYou, dateText);
                privateTextBuilder.Append('\n');
                privateTextBuilder.AppendJoin('\n', users.Where(u => u.userId != privateUserId).Select(
                                                  u => u.userId > 0
                        ? string.Format(culture, model.TimeForUserWithTelegram, u.name, u.time, u.userId)
                        : string.Format(culture, model.TimeForUserWithoutTelegram, u.name, u.time)));

                try
                {
                    await _client.SendTextMessageAsync(privateUserId, privateTextBuilder.ToString(),
                                                       ParseMode.Html, cancellationToken : stoppingToken).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "Unable to send a message to private chat");
                    await _client.SendTextMessageAsync(-1001463899405L,
                                                       $"Пользователь [{privateUserId}](tg://user?id={privateUserId}) не начал чат с ботом.",
                                                       ParseMode.Markdown, cancellationToken : stoppingToken).ConfigureAwait(false);
                }

                try
                {
                    await _context.UserRestaurantPairs
                    .UpdateOneAsync(ur => ur.RestaurantId == restaurant.ChatId && ur.UserId == privateUserId,
                                    Builders <UserRestaurantPair> .Update.SetOnInsert(ur => ur.Id, ObjectId.GenerateNewId()),
                                    new UpdateOptions { IsUpsert = true }, stoppingToken).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "Unable to save user id to UserRestaurantPairs");
                    await _client.SendTextMessageAsync(-1001463899405L, "Произошла ошибка в боте",
                                                       cancellationToken : stoppingToken).ConfigureAwait(false);
                }
            }

            if (userId == 0)
            {
                var groupTextBuilder = new StringBuilder();
                groupTextBuilder.AppendFormat(culture, model.WhoWorksAtDate, dateText, restaurant.PlaceInfo);
                groupTextBuilder.Append('\n');
                groupTextBuilder.AppendJoin('\n', users.Select(u => u.userId > 0
                    ? string.Format(culture, model.TimeForUserWithTelegram, u.name, u.time, u.userId)
                    : string.Format(culture, model.TimeForUserWithoutTelegram, u.name, u.time)));

                try
                {
                    await _client.SendTextMessageAsync(restaurant.ChatId, groupTextBuilder.ToString(),
                                                       ParseMode.Html, cancellationToken : stoppingToken).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Unable to send a message to group");
                    await _client.SendTextMessageAsync(-1001463899405L,
                                                       $"Чат {restaurant.ChatId} не доступен для ресторана {restaurant.Name}",
                                                       cancellationToken : stoppingToken).ConfigureAwait(false);
                }
            }
        }