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); } }
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); } } }