diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4ec39be
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "cSpell.enabled": false
+}
\ No newline at end of file
diff --git a/Employee.cs b/Employee.cs
index d780807..d05cc0c 100644
--- a/Employee.cs
+++ b/Employee.cs
@@ -9,6 +9,7 @@ public class Employee
public string Department { get; set; } = string.Empty; // Название отдела
public string DepartmentCode { get; set; } = string.Empty; // Код отдела
public string Position { get; set; } = string.Empty; // Должность
+ public bool Birthday { get; set; } = false;
public string? Phone { get; set; } // Опциональное поле для телефона
public string? Mobile { get; set; } // Опциональное поле для хранения номера мобильного телефона
public string? Mail { get; set; } // Опциональное поле для электронной почты
diff --git a/EmployeeContact.cs b/EmployeeContact.cs
index f6b2dc5..068f8d6 100644
--- a/EmployeeContact.cs
+++ b/EmployeeContact.cs
@@ -12,10 +12,12 @@ public class EmployeeContact
{
LastName = nameParts[0];
FirstName = nameParts[1];
+ ShortName = $"{LastName} {FirstName}";
}
else
{
FirstName = FullName;
+ ShortName = FullName;
LastName = string.Empty;
}
Department = employee.Department;
@@ -60,6 +62,7 @@ public class EmployeeContact
}
}
public string FullName { get; set; } // Полное имя сотрудника
+ public string ShortName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Department { get; set; } // Название отдела
diff --git a/FavoriteContactsManager.cs b/FavoriteContactsManager.cs
index 1e8cf44..d2bce23 100644
--- a/FavoriteContactsManager.cs
+++ b/FavoriteContactsManager.cs
@@ -1,27 +1,30 @@
using StackExchange.Redis;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+
namespace nsszcontactbot;
public class FavoriteContactsManager
{
private readonly IDatabase _redisDb;
- private const string KeyPrefix = "user:favorites:";
public FavoriteContactsManager(ConnectionMultiplexer redis)
{
_redisDb = redis.GetDatabase();
}
+ private static string GetUserKey(long userId) => $"user:favorites:{userId}";
+ private static string GetContactKey(string contactId) => $"contact:favorites:{contactId}";
+
///
/// Добавить контакт в избранное.
///
public async Task AddFavoriteAsync(long userId, string contactId)
{
- string key = GetRedisKey(userId);
- await _redisDb.SetAddAsync(key, contactId);
+ string userKey = GetUserKey(userId);
+ string contactKey = GetContactKey(contactId);
+ await Task.WhenAll(
+ _redisDb.SetAddAsync(userKey, contactId),
+ _redisDb.SetAddAsync(contactKey, userId.ToString())
+ );
}
///
@@ -29,8 +32,12 @@ public class FavoriteContactsManager
///
public async Task RemoveFavoriteAsync(long userId, string contactId)
{
- string key = GetRedisKey(userId);
- await _redisDb.SetRemoveAsync(key, contactId);
+ string userKey = GetUserKey(userId);
+ string contactKey = GetContactKey(contactId);
+ await Task.WhenAll(
+ _redisDb.SetRemoveAsync(userKey, contactId),
+ _redisDb.SetRemoveAsync(contactKey, userId.ToString())
+ );
}
///
@@ -38,7 +45,7 @@ public class FavoriteContactsManager
///
public async Task IsFavoriteAsync(long userId, string contactId)
{
- string key = GetRedisKey(userId);
+ string key = GetUserKey(userId);
return await _redisDb.SetContainsAsync(key, contactId);
}
@@ -47,10 +54,16 @@ public class FavoriteContactsManager
///
public async Task> GetAllFavoritesAsync(long userId)
{
- string key = GetRedisKey(userId);
+ string key = GetUserKey(userId);
var members = await _redisDb.SetMembersAsync(key);
- return members.Select(m => m.ToString()).ToList();
+ return [.. members.Select(m => m.ToString())];
+ }
+
+ public async Task> GetUsersWithFavoriteAsync(string contactId)
+ {
+ string contactKey = GetContactKey(contactId);
+ var members = await _redisDb.SetMembersAsync(contactKey);
+ return [.. members.Select(m => long.Parse(m.ToString()))];
}
- private string GetRedisKey(long userId) => $"{KeyPrefix}{userId}";
}
diff --git a/InlineKeyboardMarkupExtensions.cs b/InlineKeyboardMarkupExtensions.cs
index 75ad520..50a6c7f 100644
--- a/InlineKeyboardMarkupExtensions.cs
+++ b/InlineKeyboardMarkupExtensions.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Telegram.Bot.Types.ReplyMarkups;
+using Telegram.Bot.Types.ReplyMarkups;
namespace nsszcontactbot
{
@@ -14,5 +9,27 @@ namespace nsszcontactbot
var buttonRow = newRow ? markup.AddNewRow() : markup;
return buttonRow.AddButton("❌ Закрыть", "close");
}
+
+ internal static InlineKeyboardMarkup AddPrevPageButton(this InlineKeyboardMarkup markup, int currentPage, string context = "", string text = "⬅️ Назад")
+ {
+ return markup.AddButton(text, $"page/{currentPage - 1}{(string.IsNullOrEmpty(context) ? "" : "/")}{context}");
+ }
+
+ internal static InlineKeyboardMarkup AddNextPageButton(this InlineKeyboardMarkup markup, int currentPage, string context = "", string text = "Вперед ➡️")
+ {
+ return markup.AddButton(text, $"page/{currentPage + 1}{(string.IsNullOrEmpty(context) ? "" : "/")}{context}");
+ }
+
+ internal static InlineKeyboardMarkup AddPaginator(this InlineKeyboardMarkup markup, int currentPage, int totalPages, string context = "", string textPrev = "⬅️ Назад", string textNext = "Вперед ➡️")
+ {
+ if (totalPages > 1)
+ {
+ if (currentPage > 1)
+ AddPrevPageButton(markup, currentPage, context, textPrev);
+ if (currentPage < totalPages)
+ AddNextPageButton(markup, currentPage, context, textNext);
+ }
+ return markup;
+ }
}
}
diff --git a/PersistentContacts.json b/PersistentContacts.json
new file mode 100644
index 0000000..a462d05
--- /dev/null
+++ b/PersistentContacts.json
@@ -0,0 +1,115 @@
+[
+ {
+ "id": "cp",
+ "fullName": "Центральная проходная",
+ "department": "Проходные",
+ "departmentCode": 8888,
+ "position": "",
+ "phone": "8057",
+ "category": 1
+ },
+ {
+ "id": "tp",
+ "fullName": "Транспортная проходная",
+ "department": "Проходные",
+ "departmentCode": 8888,
+ "position": "",
+ "phone": "8059",
+ "category": 2
+ },
+ {
+ "id": "po701",
+ "fullName": "Пост охраны проект 701",
+ "department": "Посты охраны",
+ "departmentCode": 9999,
+ "position": "",
+ "phone": "8443",
+ "category": 2
+ },
+ {
+ "id": "po702",
+ "fullName": "Пост охраны проект 702",
+ "department": "Посты охраны",
+ "departmentCode": 9999,
+ "position": "",
+ "phone": "8168",
+ "category": 2
+ },
+ {
+ "id": "po402",
+ "fullName": "Пост охраны проект 402",
+ "department": "Посты охраны",
+ "departmentCode": 9999,
+ "position": "",
+ "phone": "8498",
+ "category": 2
+ },
+ {
+ "id": "po703",
+ "fullName": "Пост охраны проект 703",
+ "department": "Посты охраны",
+ "departmentCode": 9999,
+ "position": "",
+ "phone": "8169",
+ "category": 2
+ },
+ {
+ "id": "pov",
+ "fullName": "Пост охраны видеонаблюдения",
+ "department": "Посты охраны",
+ "departmentCode": 9999,
+ "position": "",
+ "phone": "8468",
+ "category": 2
+ },
+ {
+ "id": "mk",
+ "fullName": "Медицинский кабинет",
+ "department": "Дежурные службы",
+ "departmentCode": -1,
+ "position": "",
+ "mobile": "79312040016",
+ "phone": "8220",
+ "mail": "medkab@nssz.ru",
+ "category": 1
+ },
+ {
+ "id": "de",
+ "fullName": "Дежурный электрик",
+ "department": "Дежурные службы",
+ "departmentCode": -1,
+ "position": "",
+ "mobile": "79213973747",
+ "phone": "8143",
+ "mail": "medkab2@nssz.ru",
+ "category": 2
+ },
+ {
+ "id": "dves",
+ "fullName": "Дежурный ВЭС",
+ "department": "Дежурные службы",
+ "departmentCode": -1,
+ "position": "",
+ "phone": "8147",
+ "mobile": "79213279276",
+ "category": 2
+ },
+ {
+ "id": "dvtk",
+ "fullName": "Дежурный водоснабжения, теплоснабжения, канализации",
+ "department": "Дежурные службы",
+ "departmentCode": -1,
+ "position": "",
+ "phone": "8129",
+ "category": 2
+ },
+ {
+ "id": "durptm",
+ "fullName": "Дежурный по участку ремонта ПТМ",
+ "department": "Дежурные службы",
+ "departmentCode": -1,
+ "position": "",
+ "phone": "8142",
+ "category": 2
+ }
+]
\ No newline at end of file
diff --git a/PhotosManager.cs b/PhotosManager.cs
new file mode 100644
index 0000000..9a5142a
--- /dev/null
+++ b/PhotosManager.cs
@@ -0,0 +1,48 @@
+using StackExchange.Redis;
+using Telegram.Bot.Types;
+
+namespace nsszcontactbot;
+
+public class PhotosManager(ConnectionMultiplexer redis, string PhotoBasePath)
+{
+ private readonly IDatabase _redisDb = redis.GetDatabase();
+ private readonly string _basePath = PhotoBasePath;
+ private const string Key = "cache:photos";
+ private const string DefaultPhotoFileId = "AgACAgIAAxkDAAIDTWgtK4jsuoRTb7C4p0uhfVAOvWy2AAK1-TEb3cloSdmbxB-nuRFCAQADAgADcwADNgQ";
+
+ public async Task CacheEmployeePhotoAsync(Employee employee, string fileId, TimeSpan ttl = default)
+ {
+ string entryName = GetEntryName(employee);
+ _redisDb.HashSetAsync(Key, [new(entryName, fileId)]).ConfigureAwait(false);
+ ttl = ttl == default ? TimeSpan.FromDays(30) : ttl;
+ _redisDb.HashFieldExpireAsync(Key, [new(entryName)], ttl).ConfigureAwait(false);
+ }
+
+ public async Task GetEmployeePhotoAsync(Employee employee)
+ {
+ string entryName = GetEntryName(employee);
+ var val = await _redisDb.HashGetAsync(Key, entryName);
+ if (val != RedisValue.Null && val.HasValue)
+ {
+ return InputFile.FromFileId(val!);
+ }
+ else
+ {
+ string photoPath = Path.Combine(_basePath, $"{employee.Id}.jpg");
+ photoPath = File.Exists(photoPath) ? photoPath : Path.Combine(_basePath, $"{employee.FullName}.jpg");
+
+ if (File.Exists(photoPath))
+ {
+ var fileStream = new FileStream(
+ photoPath,
+ FileMode.Open,
+ FileAccess.Read
+ );
+ return InputFile.FromStream(fileStream);
+ }
+ return InputFile.FromFileId(DefaultPhotoFileId);;
+ }
+ }
+
+ private static string GetEntryName(Employee employee) => $"employee:{employee.Id}:fileid";
+}
diff --git a/Program.cs b/Program.cs
index 78c0219..39a0962 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,29 +1,25 @@
-using Newtonsoft.Json;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using Serilog;
+using StackExchange.Redis;
+using System.Timers;
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 List Employees = [];
+ private static List Departments = [];
private static IConfigurationRoot config;
private static FavoriteContactsManager FavoritesManager;
+ private static PhotosManager PhotosManager;
//private static ILogger Logger;
public static async Task Main(string[] args)
@@ -49,14 +45,15 @@ public class Program
});
FavoritesManager = new FavoriteContactsManager(redis);
+ PhotosManager = new PhotosManager(redis, config["Paths:PhotoBasePath"]!);
Bot = new TelegramBotClient(botToken);
- List cmds = new();
+ List cmds = [];
await Bot.SetMyCommands(cmds);
- if(!LoadEmployees())
+ if (!LoadEmployees())
{
return 1;
}
- StartUpdatingEmployeesData();
+ StartUpdatingEmployeesData(15 * 60 * 1000); // обновлять данные каждые 15 минут
var me = await Bot.GetMe();
@@ -76,6 +73,8 @@ public class Program
cancellationToken: cancellationToken
);
+ StartBirthdayNotifications();
+
Console.WriteLine("Нажмите Enter для выхода...");
Console.ReadLine();
return 0;
@@ -96,11 +95,11 @@ public class Program
{
await StartCommand(message);
}
- else if (message.Text.EndsWith("Список подразделений"))
+ else if (message.Text.EndsWith("Подразделения"))
{
await ChooseDepartment(message);
}
- else if (message.Text.EndsWith("Избранные контакты"))
+ else if (message.Text.EndsWith("Избранные"))
{
await HandleShowFavorites(message);
}
@@ -111,8 +110,7 @@ public class Program
}
else if (update.Type == UpdateType.CallbackQuery)
{
- var message = update.CallbackQuery.Message;
- string data = update.CallbackQuery.Data ?? string.Empty;
+ string data = update.CallbackQuery!.Data ?? string.Empty;
string[] dataParts = data.Split('/');
if (dataParts.Length > 0)
{
@@ -155,7 +153,16 @@ public class Program
await HandleShowEmployee(update.CallbackQuery, dataParts[1]);
break;
}
-
+
+ case "page":
+ {
+ if (dataParts.Length < 3)
+ break;
+ var arg = dataParts.Length == 3 ? dataParts[2] : dataParts[2] + "/" + dataParts[3];
+ await HandlePageNavigation(update.CallbackQuery, int.Parse(dataParts[1]), arg);
+ break;
+ }
+
case "close":
{
await HandleClose(
@@ -175,32 +182,80 @@ public class Program
private static async Task StartCommand(Message message)
{
string text = "Привет! Я бот для поиска контактов сотрудников. Введите критерии поиска или воспользуйтесь кнопками внизу.\n";
- await Bot.SendMessage(message.Chat.Id, text, replyMarkup: new string[] { "⭐️ Избранные контакты", "🏢 Список подразделений" });
+ 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();
+ Departments = [.. Employees.OrderBy(e => e.DepartmentCode).Select(e => e.Department).Distinct()];
await ShowDepartments(message.Chat.Id);
}
- private static async Task ShowDepartments(long chatId)
+ private static async Task ShowDepartments(long chatId, int page = 1, int pageSize = 10)
{
- string text = "Выберите подразделение:";
+ if (Departments.Count == 0)
+ {
+ await Bot.SendMessage(chatId, "Подразделения не найдены.");
+ return;
+ }
+
+ var totalPages = (int)Math.Ceiling((double)Departments.Count / pageSize);
+ var pagedDepartments = Departments.Skip((page - 1) * pageSize).Take(pageSize).ToList();
+
+ string text = $"Выберите подразделение (страница {page}/{totalPages}):";
var keyboardMarkup = new InlineKeyboardMarkup();
- var departmentIndex = 0;
- foreach (var department in Departments)
+ var departmentIndex = (page - 1) * pageSize;
+ foreach (var department in pagedDepartments)
{
keyboardMarkup
.AddNewRow()
.AddButton($"{department}", $"showdepartment/{departmentIndex++}");
}
+
+ if (totalPages > 1)
+ {
+ var navigationRow = keyboardMarkup.AddNewRow();
+ navigationRow.AddPaginator(page, totalPages, "departments");
+ }
+
//keyboardMarkup.AddCloseButton();
await Bot.SendMessage(chatId, text, replyMarkup: keyboardMarkup);
await Task.Delay(999);
}
+ class EmployeeComparer : IComparer
+ {
+ public int Compare(Employee? a, Employee? b)
+ {
+ if (a == null || b == null) return 0;
+
+ // Сортировка по DepartmentCode
+ int cd = CompareDepartmentCode(a.DepartmentCode, b.DepartmentCode);
+ if (cd != 0) return cd;
+
+ // Сортировка по Category
+ int cc = a.Category.CompareTo(b.Category);
+ if (cc != 0) return cc;
+
+ // Сортировка по Position (с учетом пустых значений)
+ int cp = string.Compare(a.Position ?? "", b.Position ?? "", StringComparison.OrdinalIgnoreCase);
+ if (cp != 0) return cp;
+
+ // Сортировка по FullName
+ return string.Compare(a.FullName, b.FullName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static int CompareDepartmentCode(string codeA, string codeB)
+ {
+ if (int.TryParse(codeA, out int a) && int.TryParse(codeB, out int b))
+ {
+ return a.CompareTo(b);
+ }
+ return string.Compare(codeA, codeB, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
private static async Task HandleSearchInput(Message message)
{
if (!string.IsNullOrEmpty(message.Text))
@@ -216,18 +271,25 @@ public class Program
}
// Поиск сотрудников по полям
+ var queries = query.Split(' ', StringSplitOptions.RemoveEmptyEntries);
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();
+ .Where(e =>
+ queries.All(q =>
+ {
+ var res = e.FullName.Contains(q, StringComparison.CurrentCultureIgnoreCase) ||
+ (e.Mail != null && e.Mail.Contains(q, StringComparison.CurrentCultureIgnoreCase)) ||
+ (e.Phone != null && e.Phone.Contains(q)) ||
+ (e.Mobile != null && e.Mobile.Contains(q)) ||
+ (e.IP != null && e.IP.Contains(q));
+ if (queries.Length == 1) return res;
+ return res ||
+ (e.Department != null && e.Department.Contains(q, StringComparison.CurrentCultureIgnoreCase)) ||
+ (e.Position != null && e.Position.Contains(q, StringComparison.CurrentCultureIgnoreCase));
+ }
+ ))
+ .Order(new EmployeeComparer())
+ .ToList();
// Отправка результатов поиска
await ShowEmployeeResults(message, results, null);
}
@@ -237,13 +299,14 @@ public class Program
{
if (!string.IsNullOrEmpty(message.Text))
{
- var ids = await FavoritesManager.GetAllFavoritesAsync(message.From.Id);
+ var ids = await FavoritesManager.GetAllFavoritesAsync(message.From!.Id);
// Поиск сотрудников по полям
var results = Employees
.Where(
e => ids.Contains(e.Id)
)
.OrderBy(e => e.FullName)
+ //.Order(new EmployeeComparer())
.ToList();
// Отправка результатов поиска
@@ -255,7 +318,7 @@ public class Program
{
if (callbackQuery.Message != null)
{
- var employee = Employees.Where(e => e.Id == id).First();
+ var employee = Employees.First(e => e.Id == id);
if (employee != null)
{
EmployeeContact contact = new(employee);
@@ -279,7 +342,7 @@ public class Program
vcard: contact.VCard,
replyParameters: replyParameters,
replyMarkup: keyboardMarkup,
- protectContent: true
+ protectContent: false
);
await Task.Delay(999);
}
@@ -291,14 +354,14 @@ public class Program
{
if (callbackQuery.Message != null)
{
- var employee = Employees.Where(e => e.Id == contactId).First();
+ var employee = Employees.First(e => e.Id == contactId);
if (employee != null)
{
await FavoritesManager.AddFavoriteAsync(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("addfavorite"))
+ if (cell.CallbackData!.Contains("addfavorite"))
{
cell.CallbackData = $"delfavorite/{contactId}";
cell.Text = "💔 Не избранный";
@@ -315,7 +378,7 @@ public class Program
{
if (callbackQuery.Message != null)
{
- var employee = Employees.Where(e => e.Id == contactId).First();
+ var employee = Employees.First(e => e.Id == contactId);
if (employee != null)
{
await FavoritesManager.RemoveFavoriteAsync(callbackQuery.From.Id, contactId);
@@ -347,7 +410,8 @@ public class Program
var employees = Employees
.Where(e => e.Department == Departments[departmentIndex])
- .OrderBy(e => e.FullName)
+ //.OrderBy(e => e.FullName)
+ .Order(new EmployeeComparer())
.ToList();
if (employees != null)
{
@@ -361,7 +425,7 @@ public class Program
{
if (callbackQuery.Message != null)
{
- var employee = Employees.Where(e => e.Id == id).First();
+ var employee = Employees.First(e => e.Id == id);
if (employee != null)
{
var isFavorite = await FavoritesManager.IsFavoriteAsync(callbackQuery.From.Id, id);
@@ -371,6 +435,27 @@ public class Program
await Bot.AnswerCallbackQuery(callbackQuery.Id);
}
+ private static async Task HandlePageNavigation(CallbackQuery callbackQuery, int page, string context)
+ {
+ if (callbackQuery.Message != null)
+ {
+ await Bot.DeleteMessage(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId);
+ var messageHash = (callbackQuery.Message.Chat.Id << 32) | (uint)callbackQuery.Message.Id;
+
+ if (context.Contains("employees"))
+ {
+ long hash = long.Parse(context.Split('/')[1]);
+ var results = _resultsCache[hash];
+ await ShowEmployeeResults(callbackQuery.Message, results, null, page);
+ //_resultsCache.Remove(messageHash);
+ }
+ else if (context == "departments")
+ {
+ await ShowDepartments(callbackQuery.Message.Chat.Id, page);
+ }
+ }
+ await Bot.AnswerCallbackQuery(callbackQuery.Id);
+ }
private static async Task HandleClose(CallbackQuery callbackQuery, string? nextAction)
{
@@ -410,65 +495,81 @@ public class Program
var card = CreateEmployeeCard(employee);
- string photoPath = Path.Combine(config["Paths:PhotoBasePath"]!, $"{employee.FullName}.jpg");
- try
+ var photo = await PhotosManager.GetEmployeePhotoAsync(employee);
+
+ if (photo != null)
{
- await using var fileStream = new FileStream(
- photoPath,
- FileMode.Open,
- FileAccess.Read
- );
-
- await Bot.SendPhoto(
+ var msg = await Bot.SendPhoto(
chatId,
- fileStream,
+ photo,
caption: card,
parseMode: ParseMode.Html,
replyMarkup: keyboardMarkup
);
+ if (photo.FileType == FileType.Stream)
+ {
+ FileStream? stream = (photo as InputFileStream)!.Content as FileStream;
+ stream?.Dispose();
+ await PhotosManager.CacheEmployeePhotoAsync(employee, msg.Photo!.FirstOrDefault()!.FileId); // TODO test !
+ //await PhotosManager.CacheEmployeePhotoAsync(employee, msg.Photo.FirstOrDefault().FileId);
+ }
}
- catch (FileNotFoundException)
+ else
{
await Bot.SendMessage(
chatId,
card,
ParseMode.Html,
replyMarkup: keyboardMarkup,
- protectContent: true
+ protectContent: false
);
}
- finally
- {
- await Task.Delay(999);
- }
+ await Task.Delay(999);
+
}
}
- private static async Task ShowEmployeeResults(Message message, List results, bool? isFavorite = false)
+ private static Dictionary> _resultsCache = [];
+
+ private static async Task ShowEmployeeResults(Message message, List results, bool? isFavorite = false, int page = 1, int pageSize = 10)
{
if (results.Any())
{
- if (results.Count < 10)
+ if (results.Count <= pageSize)
{
foreach (var employee in results)
{
- bool bFavorite = isFavorite.HasValue ? isFavorite == true : await FavoritesManager.IsFavoriteAsync(message.From.Id, employee.Id);
+ bool bFavorite = isFavorite.HasValue ? isFavorite == true : await FavoritesManager.IsFavoriteAsync(message.Chat.Id, employee.Id);
await ShowEmployeeCard(message.Chat.Id, employee, bFavorite);
}
}
else
{
- string text = "Вот, что удалось найти:";
+ var totalPages = (int)Math.Ceiling((double)results.Count / pageSize);
+ var pagedResults = results.Skip((page - 1) * pageSize).Take(pageSize).ToList();
+
+ string text = $"Найдено {results.Count} сотрудников (страница {page}/{totalPages}):";
var keyboardMarkup = new InlineKeyboardMarkup();
- foreach (var employee in results)
+ foreach (var employee in pagedResults)
{
+ var title = CreateEmployeeTitle(employee);
keyboardMarkup
.AddNewRow()
- .AddButton(employee.FullName, $"showemployee/{employee.Id}");
+ .AddButton(title, $"showemployee/{employee.Id}");
}
- await Bot.SendMessage(message.Chat.Id, text, replyMarkup: keyboardMarkup, protectContent: true);
+ if (totalPages > 1)
+ {
+ var messageHash = results.GetHashCode();// (message.Chat.Id << 32) | (uint)message.Id;
+ _resultsCache[messageHash] = results;
+
+ var navigationRow = keyboardMarkup.AddNewRow();
+ navigationRow.AddPaginator(page, totalPages, $"employees/{messageHash}");
+ }
+
+ //keyboardMarkup.AddCloseButton();
+ await Bot.SendMessage(message.Chat.Id, text, replyMarkup: keyboardMarkup, protectContent: false);
await Task.Delay(999);
}
}
@@ -481,9 +582,14 @@ public class Program
static string GetEventStatus(Employee employee)
{
+ string result = "";
+ if (employee.Birthday)
+ {
+ result += "🎂 Празднует день рождения\n";
+ }
if (employee.StartDate == null || employee.EndDate == null)
{
- return string.Empty;
+ return result;
}
DateTime today = DateTime.Now;
TimeSpan oneWeekBeforeEvent = (DateTime)employee.StartDate - today;
@@ -491,16 +597,58 @@ public class Program
if (employee.StartDate <= today && today <= employee.EndDate)
{
// Сотрудник участвует в событии
- return $"🟥 {employee.State} с {employee.StartDate:dd.MM.yyyy} по {employee.EndDate:dd.MM.yyyy}";
+ result += $"🟥 {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}";
+ result += $"🟩 {employee.State} с {employee.StartDate:dd.MM.yyyy} по {employee.EndDate:dd.MM.yyyy}";
}
+ return result;
// Если событие не указано или неактуально
- return string.Empty;
+ //return string.Empty;
+ }
+
+ private static string CreateEmployeeTitle(Employee employee)
+ {
+ EmployeeContact contact = new(employee);
+
+ string prefix = string.Empty;
+
+ if (employee.StartDate != null || employee.EndDate != null)
+ {
+ DateTime today = DateTime.Now;
+ TimeSpan oneWeekBeforeEvent = (DateTime)employee.StartDate! - today;
+
+ if (employee.StartDate <= today && today <= employee.EndDate)
+ {
+ // Сотрудник участвует в событии
+ prefix += "🟥";
+ }
+ else if (oneWeekBeforeEvent.TotalDays <= 7 && oneWeekBeforeEvent.TotalDays > 0)
+ {
+ // Событие начнется через неделю
+ prefix += "🟩";
+ }
+
+ }
+
+ if (employee.Birthday)
+ {
+ prefix += "🎂";
+ }
+
+ if (string.IsNullOrEmpty(prefix))
+ {
+ //prefix = "👤 ";
+ }
+ else
+ {
+ prefix += " ";
+ }
+
+ return $"{prefix}{contact.ShortName}";
}
private static string CreateEmployeeCard(Employee employee)
@@ -508,8 +656,10 @@ public class Program
EmployeeContact contact = new(employee);
string card =
$"👤 {contact.FullName}\n"
- + $"🏢 {contact.Department}\n"
- + $"🎓 {contact.Position}\n";
+ + $"🏢 {contact.Department}\n";
+
+ if (!string.IsNullOrEmpty(contact.Position))
+ card += $"🎓 {contact.Position}\n";
if (!string.IsNullOrEmpty(contact.Mail))
card += $"✉️ {contact.Mail}\n";
@@ -527,31 +677,6 @@ public class Program
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)
{
@@ -572,23 +697,78 @@ public class Program
{
try
{
- // Загрузка данных сотрудников из JSON файла при наличии изменений
- var json = System.IO.File.ReadAllText(config["Paths:ContactsJsonPath"]!);
- int newHash = json.GetHashCode();
- if (prevHash == newHash)
+ string filePath = config["Paths:ContactsJsonPath"] ?? string.Empty;
+ if (!string.IsNullOrEmpty(filePath))
{
+ string json;
+ if (filePath.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
+ {
+ using var client = new HttpClient();
+ using HttpResponseMessage response = client.GetAsync(filePath).Result;
+ using HttpContent content = response.Content;
+ json = content.ReadAsStringAsync().Result;
+ }
+ else
+ {
+ json = File.ReadAllText(filePath);
+ }
+ // Загрузка данных сотрудников из JSON файла при наличии изменений
+ int newHash = json.GetHashCode();
+ if (prevHash == newHash)
+ {
+ return true;
+ }
+ prevHash = newHash;
+ Employees = JsonConvert.DeserializeObject>(json)!;
+ }
+ else
+ {
+ Employees.Clear();
+ }
+
+ filePath = config["Paths:PersistentContactsJsonPath"] ?? string.Empty;
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ var json = File.ReadAllText(filePath);
+ Employees.AddRange(JsonConvert.DeserializeObject>(json)!);
+ }
+ if (Employees.Count > 0)
+ {
+ Log.Information("Загружены данные о {Count} сотрудниках", Employees.Count);
return true;
}
- prevHash = newHash;
- Employees = JsonConvert.DeserializeObject>(json)!;
- Log.Information("Загружены данные о {Count} сотрудниках", Employees.Count);
- return true;
- } catch (Exception ex) {
+ Log.Error("Нет данных о сотрудниках");
+ return false;
+
+ }
+ catch (Exception ex)
+ {
Log.Error(ex, "Ошибка при загрузке списка сотрудников");
return false;
}
}
+ private static void StartBirthdayNotifications()
+ {
+ var timer = new Timer(TimeSpan.FromHours(24).TotalMilliseconds);
+ timer.Elapsed += async (sender, e) =>
+ {
+ var today = DateTime.Today;
+ var birthdayEmployees = Employees.Where(e => e.Birthday).ToList();
+ foreach (var employee in birthdayEmployees)
+ {
+ var users = await FavoritesManager.GetUsersWithFavoriteAsync(employee.Id);
+ foreach (var userId in users)
+ {
+ await Bot.SendMessage(userId, $"🎂 Сегодня день рождения у {employee.FullName}!");
+ await Task.Delay(999);
+ }
+ }
+ };
+ timer.AutoReset = true;
+ timer.Start();
+ }
+
private static Task HandleErrorAsync(
ITelegramBotClient botClient,
Exception exception,
diff --git a/appsettings.json b/appsettings.json
index ce0412f..17a287b 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -1,21 +1,27 @@
{
"Paths": {
- "ContactsJsonPath": "data/contacts.json",
- "PhotoBasePath": "data/photos"
+ "ContactsJsonPath": "http://contacts.nssz.local/data/contacts.json",
+ "PersistentContactsJsonPath": "PersistentContacts.json",
+ "PhotoBasePath": "photos"
},
"Telegram": {
"BotToken": "8155021946:AAGbfJnLa0sTWxqFB9yjbXhbTedBw4wvcQE"
},
"Redis": {
- "Configuration": "redis-15840.crce198.eu-central-1-3.ec2.redns.redis-cloud.com:15840",
- "User": "tgbot",
- "Password": "0aCKMee9Ocq88KzfjWyC9AmB25tLiY93%"
+ "Configuration": "contacts.nssz.local:6379",
+ "User": "default",
+ "Password": ""
},
"Serilog": {
- "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
+ "Using": [
+ "Serilog.Sinks.Console",
+ "Serilog.Sinks.File"
+ ],
"MinimumLevel": "Debug",
"WriteTo": [
- { "Name": "Console" },
+ {
+ "Name": "Console"
+ },
{
"Name": "File",
"Args": {
diff --git a/nsszcontactbot.csproj b/nsszcontactbot.csproj
index 10e6652..4caf798 100644
--- a/nsszcontactbot.csproj
+++ b/nsszcontactbot.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net10.0
enable
enable
@@ -20,6 +20,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..3998599
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,3 @@
+dotnet build
+cd bin/Debug/net7.0
+screen ./nsszcontactbot
\ No newline at end of file