From 5acf3f9c9637c7c4ef10052a343c3a1be267d2a7 Mon Sep 17 00:00:00 2001 From: Oleg-Nevsky Date: Wed, 12 Aug 2026 16:25:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D0=B5=D1=80=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B0=D0=BA=D1=82=D0=BE=D0=B2?= =?UTF-8?q?,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D1=84=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=B9=20=D1=81=D0=BE?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=B4=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + InlineKeyboardMarkupExtensions.cs | 4 +- PhotosManager.cs | 4 +- Program.cs | 63 ++++++++++++++++++++++++------- appsettings.json | 3 +- nsszcontactbot.csproj | 1 + run.cmd | 4 ++ 7 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 run.cmd diff --git a/.gitignore b/.gitignore index b68197c..b940e92 100644 --- a/.gitignore +++ b/.gitignore @@ -438,3 +438,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +unique_contacts_backup/* \ No newline at end of file diff --git a/InlineKeyboardMarkupExtensions.cs b/InlineKeyboardMarkupExtensions.cs index 50a6c7f..0122cb8 100644 --- a/InlineKeyboardMarkupExtensions.cs +++ b/InlineKeyboardMarkupExtensions.cs @@ -4,10 +4,10 @@ namespace nsszcontactbot { internal static class InlineKeyboardMarkupExtensions { - internal static InlineKeyboardMarkup AddCloseButton(this InlineKeyboardMarkup markup, bool newRow = true) + internal static InlineKeyboardMarkup AddCloseButton(this InlineKeyboardMarkup markup, string text = "❌ Закрыть", bool newRow = true) { var buttonRow = newRow ? markup.AddNewRow() : markup; - return buttonRow.AddButton("❌ Закрыть", "close"); + return buttonRow.AddButton(text, "close"); } internal static InlineKeyboardMarkup AddPrevPageButton(this InlineKeyboardMarkup markup, int currentPage, string context = "", string text = "⬅️ Назад") diff --git a/PhotosManager.cs b/PhotosManager.cs index 9a5142a..3117bb9 100644 --- a/PhotosManager.cs +++ b/PhotosManager.cs @@ -13,9 +13,11 @@ public class PhotosManager(ConnectionMultiplexer redis, string PhotoBasePath) public async Task CacheEmployeePhotoAsync(Employee employee, string fileId, TimeSpan ttl = default) { string entryName = GetEntryName(employee); +#pragma warning disable CS4014 // Так как этот вызов не ожидается, выполнение существующего метода продолжается до тех пор, пока вызов не будет завершен _redisDb.HashSetAsync(Key, [new(entryName, fileId)]).ConfigureAwait(false); ttl = ttl == default ? TimeSpan.FromDays(30) : ttl; _redisDb.HashFieldExpireAsync(Key, [new(entryName)], ttl).ConfigureAwait(false); +#pragma warning restore CS4014 // Так как этот вызов не ожидается, выполнение существующего метода продолжается до тех пор, пока вызов не будет завершен } public async Task GetEmployeePhotoAsync(Employee employee) @@ -40,7 +42,7 @@ public class PhotosManager(ConnectionMultiplexer redis, string PhotoBasePath) ); return InputFile.FromStream(fileStream); } - return InputFile.FromFileId(DefaultPhotoFileId);; + return InputFile.FromFileId(DefaultPhotoFileId); ; } } diff --git a/Program.cs b/Program.cs index 39a0962..cbad430 100644 --- a/Program.cs +++ b/Program.cs @@ -2,6 +2,8 @@ using Newtonsoft.Json; using Serilog; using StackExchange.Redis; +using System.IO.Hashing; +using System.Text; using System.Timers; using Telegram.Bot; using Telegram.Bot.Polling; @@ -14,12 +16,12 @@ namespace nsszcontactbot; public class Program { - private static ITelegramBotClient Bot; + private static ITelegramBotClient Bot = default!; private static List Employees = []; private static List Departments = []; - private static IConfigurationRoot config; - private static FavoriteContactsManager FavoritesManager; - private static PhotosManager PhotosManager; + private static IConfigurationRoot config = default!; + private static FavoriteContactsManager FavoritesManager = default!; + private static PhotosManager PhotosManager = default!; //private static ILogger Logger; public static async Task Main(string[] args) @@ -57,7 +59,7 @@ public class Program var me = await Bot.GetMe(); - Log.Information($"Бот запущен: @{me.Username}"); + Log.Information("Бот запущен: @{Username}", me.Username); // Запуск обработки обновлений var cancellationToken = new CancellationTokenSource().Token; @@ -89,7 +91,7 @@ public class Program if (update.Type == UpdateType.Message && update.Message?.Text != null) { var message = update.Message; - Log.Debug($"INPUT: from={message.From?.Username} text={message.Text}"); + Log.Debug("Входящий текст (From=\"{Username}\", MessageId=\"{MessageId}\"): {Text}", message.From?.ToString() ?? "", message.Id, message.Text); if (message.Text.StartsWith("/start")) { @@ -97,7 +99,7 @@ public class Program } else if (message.Text.EndsWith("Подразделения")) { - await ChooseDepartment(message); + await HandleShowDepartments(message); } else if (message.Text.EndsWith("Избранные")) { @@ -111,6 +113,8 @@ public class Program else if (update.Type == UpdateType.CallbackQuery) { string data = update.CallbackQuery!.Data ?? string.Empty; + var callbackQuery = update.CallbackQuery!; + Log.Debug("Нажатие кнопки (From=\"{Username}\", CallbackQueryId=\"{CallbackQueryId}\"): {Data}", callbackQuery.From?.ToString() ?? "", callbackQuery.Id, data); string[] dataParts = data.Split('/'); if (dataParts.Length > 0) { @@ -185,7 +189,7 @@ public class Program await Bot.SendMessage(message.Chat.Id, text, replyMarkup: new string[] { "❤️ Избранные", "🏢 Подразделения" }); } - private static async Task ChooseDepartment(Message message) + private static async Task HandleShowDepartments(Message message) { Departments = [.. Employees.OrderBy(e => e.DepartmentCode).Select(e => e.Department).Distinct()]; await ShowDepartments(message.Chat.Id); @@ -382,10 +386,10 @@ public class Program if (employee != null) { await FavoritesManager.RemoveFavoriteAsync(callbackQuery.From.Id, contactId); - foreach (var row in callbackQuery.Message.ReplyMarkup.InlineKeyboard) + foreach (var row in callbackQuery.Message.ReplyMarkup!.InlineKeyboard) foreach (var cell in row) { - if (cell.CallbackData.Contains("delfavorite")) + if (cell.CallbackData!.Contains("delfavorite")) { cell.CallbackData = $"addfavorite/{contactId}"; cell.Text = "❤️ В избранные"; @@ -533,7 +537,7 @@ public class Program private static async Task ShowEmployeeResults(Message message, List results, bool? isFavorite = false, int page = 1, int pageSize = 10) { - if (results.Any()) + if (results.Count != 0) { if (results.Count <= pageSize) { @@ -670,7 +674,7 @@ public class Program card += $"📞 {contact.ExtensionNumber}\n"; if (!string.IsNullOrEmpty(employee.IP)) - card += $"🌐 {employee.IP}\n"; + card += $"🖥 {employee.IP}\n"; card += GetEventStatus(employee); @@ -692,7 +696,7 @@ public class Program LoadEmployees(); } - private static int prevHash = 0; + private static string prevHash = string.Empty; private static bool LoadEmployees() { try @@ -713,13 +717,19 @@ public class Program json = File.ReadAllText(filePath); } // Загрузка данных сотрудников из JSON файла при наличии изменений - int newHash = json.GetHashCode(); + var newHash = CalculateHash(json); if (prevHash == newHash) { return true; } prevHash = newHash; Employees = JsonConvert.DeserializeObject>(json)!; + + var backupFilename = BackupContactsJSON(json); + if (!string.IsNullOrEmpty(backupFilename)) + { + Log.Debug("Данные обновлены, создана резервная копия: {BackupFilename}", backupFilename); + } } else { @@ -748,6 +758,31 @@ public class Program } } + private static string CalculateHash(string data) + { + byte[] hashBytes = Crc32.Hash(Encoding.UTF8.GetBytes(data)); + return Convert.ToHexString(hashBytes); + } + private static string BackupContactsJSON(string json) + { + ArgumentException.ThrowIfNullOrEmpty(json, nameof(json)); + var backupDirectory = config["Paths:ContactsBackupDirectory"] ?? string.Empty; + if (!string.IsNullOrEmpty(backupDirectory)) + { + if (!Directory.Exists(backupDirectory)) + { + Directory.CreateDirectory(backupDirectory); + } + + var hashString = CalculateHash(json).ToUpperInvariant(); + + var filename = Path.GetFullPath(Path.Combine(backupDirectory, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Employees.Count}_contacts_{hashString}.json")); + File.WriteAllText(filename, json); + return filename; + } + return string.Empty; + } + private static void StartBirthdayNotifications() { var timer = new Timer(TimeSpan.FromHours(24).TotalMilliseconds); diff --git a/appsettings.json b/appsettings.json index 17a287b..55a1ffb 100644 --- a/appsettings.json +++ b/appsettings.json @@ -2,7 +2,8 @@ "Paths": { "ContactsJsonPath": "http://contacts.nssz.local/data/contacts.json", "PersistentContactsJsonPath": "PersistentContacts.json", - "PhotoBasePath": "photos" + "PhotoBasePath": "photos", + "ContactsBackupDirectory": "unique_contacts_backup" }, "Telegram": { "BotToken": "8155021946:AAGbfJnLa0sTWxqFB9yjbXhbTedBw4wvcQE" diff --git a/nsszcontactbot.csproj b/nsszcontactbot.csproj index 4caf798..cc778c3 100644 --- a/nsszcontactbot.csproj +++ b/nsszcontactbot.csproj @@ -14,6 +14,7 @@ + diff --git a/run.cmd b/run.cmd new file mode 100644 index 0000000..dc52b91 --- /dev/null +++ b/run.cmd @@ -0,0 +1,4 @@ +@echo off +dotnet build -c Release +cd bin\Release\net10.0 +.\nsszcontactbot