using Newtonsoft.Json; using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog; using System.Timers; using Serilog.Core; using NRedisStack; using NRedisStack.RedisStackCommands; using Timer = System.Timers.Timer; using StackExchange.Redis; namespace nsszcontactbot; public class Program { private static ITelegramBotClient Bot; private static List Employees = new(); private static List Departments = new(); private static IConfigurationRoot config; private static FavoriteContactsManager FavoritesManager; //private static ILogger Logger; public static async Task Main(string[] args) { config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(config).CreateLogger(); var botToken = config["Telegram:BotToken"]; if (botToken == null) { Log.Error("Токен Telegram-бота не указан в конфигурации."); return 1; } ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(config["Redis:Configuration"]!, (conf) => { conf.User = config["Redis:User"]; conf.Password = config["Redis:Password"]; }); FavoritesManager = new FavoriteContactsManager(redis); Bot = new TelegramBotClient(botToken); List cmds = new(); await Bot.SetMyCommands(cmds); if(!LoadEmployees()) { return 1; } StartUpdatingEmployeesData(); var me = await Bot.GetMe(); Log.Information($"Бот запущен: @{me.Username}"); // Запуск обработки обновлений var cancellationToken = new CancellationTokenSource().Token; ReceiverOptions receiverOptions = new() { AllowedUpdates = Array.Empty() // receive all update types except ChatMember related updates }; Bot.StartReceiving( HandleUpdateAsync, HandleErrorAsync, receiverOptions: receiverOptions, cancellationToken: cancellationToken ); Console.WriteLine("Нажмите Enter для выхода..."); Console.ReadLine(); return 0; } private static async Task HandleUpdateAsync( ITelegramBotClient botClient, Update update, CancellationToken cancellationToken ) { if (update.Type == UpdateType.Message && update.Message?.Text != null) { var message = update.Message; Log.Debug($"INPUT: from={message.From?.Username} text={message.Text}"); if (message.Text.StartsWith("/start")) { await StartCommand(message); } else if (message.Text.EndsWith("Список подразделений")) { await ChooseDepartment(message); } else if (message.Text.EndsWith("Избранные контакты")) { await HandleShowFavorites(message); } else { await HandleSearchInput(message); } } else if (update.Type == UpdateType.CallbackQuery) { var message = update.CallbackQuery.Message; string data = update.CallbackQuery.Data ?? string.Empty; string[] dataParts = data.Split('/'); if (dataParts.Length > 0) { switch (dataParts[0]) { case "addcontact": { await HandleAddContact(update.CallbackQuery, dataParts[1]); break; } case "addfavorite": { if (dataParts.Length != 2) break; await HandleAddFavorite(update.CallbackQuery, dataParts[1]); break; } case "delfavorite": { if (dataParts.Length != 2) break; await HandleDelFavorite(update.CallbackQuery, dataParts[1]); break; } case "showdepartment": { if (dataParts.Length != 2) break; await HandleShowDepartment(update.CallbackQuery, int.Parse(dataParts[1])); break; } case "showemployee": { if (dataParts.Length != 2) break; await HandleShowEmployee(update.CallbackQuery, dataParts[1]); break; } case "close": { await HandleClose( update.CallbackQuery, dataParts.Length > 1 ? dataParts[1] : null ); break; } default: break; } } } } private static async Task StartCommand(Message message) { string text = "Привет! Я бот для поиска контактов сотрудников. Введите критерии поиска или воспользуйтесь кнопками внизу.\n"; await Bot.SendMessage(message.Chat.Id, text, replyMarkup: new string[] { "⭐️ Избранные контакты", "🏢 Список подразделений" }); } private static async Task ChooseDepartment(Message message) { Departments = Employees.Select(e => e.Department).Distinct().ToList(); await ShowDepartments(message.Chat.Id); } private static async Task ShowDepartments(long chatId) { string text = "Выберите подразделение:"; var keyboardMarkup = new InlineKeyboardMarkup(); var departmentIndex = 0; foreach (var department in Departments) { keyboardMarkup .AddNewRow() .AddButton($"{department}", $"showdepartment/{departmentIndex++}"); } //keyboardMarkup.AddCloseButton(); await Bot.SendMessage(chatId, text, replyMarkup: keyboardMarkup); await Task.Delay(999); } private static async Task HandleSearchInput(Message message) { if (!string.IsNullOrEmpty(message.Text)) { string query = message.Text.Trim().ToLower(); if (query.Length < 4) { await Bot.SendMessage( message.Chat.Id, "Минимальная длина строки поиска - 4 символа." ); return; } // Поиск сотрудников по полям var results = Employees .Where( e => e.FullName.ToLower().Contains(query) || (e.Mail != null && e.Mail.ToLower().Contains(query)) || (e.Phone != null && e.Phone.Contains(query)) || (e.Mobile != null && e.Mobile.Contains(query)) || (e.IP != null && e.IP.Contains(query)) ) .OrderBy(e => e.FullName.ToLower().IndexOf(query)) .ToList(); // Отправка результатов поиска await ShowEmployeeResults(message, results, null); } } private static async Task HandleShowFavorites(Message message) { if (!string.IsNullOrEmpty(message.Text)) { var ids = await FavoritesManager.GetAllFavoritesAsync(message.From.Id); // Поиск сотрудников по полям var results = Employees .Where( e => ids.Contains(e.Id) ) .OrderBy(e => e.FullName) .ToList(); // Отправка результатов поиска await ShowEmployeeResults(message, results, true); } } private static async Task HandleAddContact(CallbackQuery callbackQuery, string id) { if (callbackQuery.Message != null) { var employee = Employees.Where(e => e.Id == id).First(); if (employee != null) { EmployeeContact contact = new(employee); string phoneNumber = !string.IsNullOrEmpty(contact.Mobile) ? contact.Mobile : contact.Phone; var replyParameters = new ReplyParameters() { MessageId = callbackQuery.Message.MessageId, QuoteParseMode = ParseMode.Html, Quote = $"{contact.FullName}" }; var keyboardMarkup = new InlineKeyboardMarkup() //.AddNewRow() .AddCloseButton(); await Bot.SendContact( callbackQuery.Message.Chat.Id, phoneNumber, contact.FirstName, lastName: contact.LastName, vcard: contact.VCard, replyParameters: replyParameters, replyMarkup: keyboardMarkup, protectContent: true ); await Task.Delay(999); } } await Bot.AnswerCallbackQuery(callbackQuery.Id); } private static async Task HandleAddFavorite(CallbackQuery callbackQuery, string contactId) { if (callbackQuery.Message != null) { var employee = Employees.Where(e => e.Id == contactId).First(); if (employee != null) { await FavoritesManager.AddFavoriteAsync(callbackQuery.From.Id, contactId); foreach (var row in callbackQuery.Message.ReplyMarkup.InlineKeyboard) foreach (var cell in row) { if (cell.CallbackData.Contains("addfavorite")) { cell.CallbackData = $"delfavorite/{contactId}"; cell.Text = "💔 Не избранный"; } } await Bot.EditMessageReplyMarkup(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId, callbackQuery.Message.ReplyMarkup); await Bot.AnswerCallbackQuery(callbackQuery.Id, "✔️ Контакт добавлен в избранные"); } } } private static async Task HandleDelFavorite(CallbackQuery callbackQuery, string contactId) { if (callbackQuery.Message != null) { var employee = Employees.Where(e => e.Id == contactId).First(); if (employee != null) { await FavoritesManager.RemoveFavoriteAsync(callbackQuery.From.Id, contactId); foreach (var row in callbackQuery.Message.ReplyMarkup.InlineKeyboard) foreach (var cell in row) { if (cell.CallbackData.Contains("delfavorite")) { cell.CallbackData = $"addfavorite/{contactId}"; cell.Text = "❤️ В избранные"; } } await Bot.EditMessageReplyMarkup(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId, callbackQuery.Message.ReplyMarkup); await Bot.AnswerCallbackQuery(callbackQuery.Id, "✔️ Контакт удален из избранных"); } } } private static async Task HandleShowDepartment(CallbackQuery callbackQuery, int departmentIndex) { await Bot.AnswerCallbackQuery(callbackQuery.Id); if (callbackQuery.Message != null) { await Bot.DeleteMessage( callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId ); var employees = Employees .Where(e => e.Department == Departments[departmentIndex]) .OrderBy(e => e.FullName) .ToList(); if (employees != null) { await ShowEmployeeResults(callbackQuery.Message, employees, null); } } //await Bot.AnswerCallbackQueryAsync(callbackQuery.Id); } private static async Task HandleShowEmployee(CallbackQuery callbackQuery, string id) { if (callbackQuery.Message != null) { var employee = Employees.Where(e => e.Id == id).First(); if (employee != null) { var isFavorite = await FavoritesManager.IsFavoriteAsync(callbackQuery.From.Id, id); await ShowEmployeeCard(callbackQuery.Message.Chat.Id, employee, isFavorite, closeButton: true); } } await Bot.AnswerCallbackQuery(callbackQuery.Id); } private static async Task HandleClose(CallbackQuery callbackQuery, string? nextAction) { if (callbackQuery.Message != null) { await Bot.DeleteMessage( callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId ); await Task.Delay(500); } await Bot.AnswerCallbackQuery(callbackQuery.Id); } private static async Task ShowEmployeeCard(long chatId, Employee employee, bool isFavorite = false, bool contactButton = true, bool favoriteButton = true, bool closeButton = false) { { var keyboardMarkup = new InlineKeyboardMarkup(); var keyboardRow = keyboardMarkup.AddNewRow(); if (contactButton) keyboardRow.AddButton("👤 Контакт", $"addcontact/{employee.Id}"); if (favoriteButton) { if (isFavorite) { keyboardRow.AddButton("💔 Не избранный", $"delfavorite/{employee.Id}"); } else { keyboardRow.AddButton("❤️ В избранные", $"addfavorite/{employee.Id}"); } } if (closeButton) keyboardRow.AddCloseButton(); var card = CreateEmployeeCard(employee); string photoPath = Path.Combine(config["Paths:PhotoBasePath"]!, $"{employee.FullName}.jpg"); try { await using var fileStream = new FileStream( photoPath, FileMode.Open, FileAccess.Read ); await Bot.SendPhoto( chatId, fileStream, caption: card, parseMode: ParseMode.Html, replyMarkup: keyboardMarkup ); } catch (FileNotFoundException) { await Bot.SendMessage( chatId, card, ParseMode.Html, replyMarkup: keyboardMarkup, protectContent: true ); } finally { await Task.Delay(999); } } } private static async Task ShowEmployeeResults(Message message, List results, bool? isFavorite = false) { if (results.Any()) { if (results.Count < 10) { foreach (var employee in results) { bool bFavorite = isFavorite.HasValue ? isFavorite == true : await FavoritesManager.IsFavoriteAsync(message.From.Id, employee.Id); await ShowEmployeeCard(message.Chat.Id, employee, bFavorite); } } else { string text = "Вот, что удалось найти:"; var keyboardMarkup = new InlineKeyboardMarkup(); foreach (var employee in results) { keyboardMarkup .AddNewRow() .AddButton(employee.FullName, $"showemployee/{employee.Id}"); } await Bot.SendMessage(message.Chat.Id, text, replyMarkup: keyboardMarkup, protectContent: true); await Task.Delay(999); } } else { await Bot.SendMessage(message.Chat.Id, "🔍 Извините, мы ничего не нашли."); await Task.Delay(999); } } static string GetEventStatus(Employee employee) { if (employee.StartDate == null || employee.EndDate == null) { return string.Empty; } DateTime today = DateTime.Now; TimeSpan oneWeekBeforeEvent = (DateTime)employee.StartDate - today; if (employee.StartDate <= today && today <= employee.EndDate) { // Сотрудник участвует в событии return $"🟥 {employee.State} с {employee.StartDate:dd.MM.yyyy} по {employee.EndDate:dd.MM.yyyy}"; } else if (oneWeekBeforeEvent.TotalDays <= 7 && oneWeekBeforeEvent.TotalDays > 0) { // Событие начнется через неделю return $"🟩 {employee.State} с {employee.StartDate:dd.MM.yyyy} по {employee.EndDate:dd.MM.yyyy}"; } // Если событие не указано или неактуально return string.Empty; } private static string CreateEmployeeCard(Employee employee) { EmployeeContact contact = new(employee); string card = $"👤 {contact.FullName}\n" + $"🏢 {contact.Department}\n" + $"🎓 {contact.Position}\n"; if (!string.IsNullOrEmpty(contact.Mail)) card += $"✉️ {contact.Mail}\n"; if (!string.IsNullOrEmpty(contact.Mobile)) card += $"📱 {contact.Mobile}\n"; if (!string.IsNullOrEmpty(contact.ExtensionNumber)) card += $"📞 {contact.ExtensionNumber}\n"; if (!string.IsNullOrEmpty(employee.IP)) card += $"🌐 {employee.IP}\n"; card += GetEventStatus(employee); return card; } private static string CreateEmployeeCard2(Employee employee) { string photoUrl = $"https://contacts.int.nssz.ru/data/photos/{employee.FullName.Replace(" ", "%20")}.jpg"; // Формирование пути к фотографии string card = $"👤 {employee.FullName}\n" + $"🏢 Отдел: {employee.Department}\n" + $"🎓 Должность: {employee.Position}\n" + $"📸 Фото: Посмотреть фото\n"; // Добавление ссылки на фото if (!string.IsNullOrEmpty(employee.Mail)) card += $"✉️ Почта: {employee.Mail}\n"; if (!string.IsNullOrEmpty(employee.Phone)) card += $"📞 Телефон: {employee.Phone}\n"; if (!string.IsNullOrEmpty(employee.Mobile)) card += $"📱 Мобильный: +{employee.Mobile}\n"; if (!string.IsNullOrEmpty(employee.IP)) card += $"🌐 IP: {employee.IP}\n"; card += GetEventStatus(employee); return card; } private static Timer? updateTimer = null; private static void StartUpdatingEmployeesData(int intervalMs = 1 * 60 * 60 * 1000) { if (updateTimer != null) return; updateTimer = new Timer(intervalMs); updateTimer.Elapsed += UpdateTimer_Elapsed; updateTimer.AutoReset = true; // Запускать снова после завершения updateTimer.Enabled = true; } private static void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) { LoadEmployees(); } private static int prevHash = 0; private static bool LoadEmployees() { try { // Загрузка данных сотрудников из JSON файла при наличии изменений var json = System.IO.File.ReadAllText(config["Paths:ContactsJsonPath"]!); int newHash = json.GetHashCode(); if (prevHash == newHash) { return true; } prevHash = newHash; Employees = JsonConvert.DeserializeObject>(json)!; Log.Information("Загружены данные о {Count} сотрудниках", Employees.Count); return true; } catch (Exception ex) { Log.Error(ex, "Ошибка при загрузке списка сотрудников"); return false; } } private static Task HandleErrorAsync( ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken ) { Log.Error(exception, "Произошла ошибка"); return Task.CompletedTask; } }