602 lines
22 KiB
C#
602 lines
22 KiB
C#
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<Employee> Employees = new();
|
||
private static List<string> Departments = new();
|
||
private static IConfigurationRoot config;
|
||
private static FavoriteContactsManager FavoritesManager;
|
||
//private static ILogger<Program> Logger;
|
||
|
||
public static async Task<int> 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<BotCommand> 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<UpdateType>() // 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 = $"<b>{contact.FullName}</b>"
|
||
};
|
||
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<Employee> 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 =
|
||
$"👤 <b>{contact.FullName}</b>\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 =
|
||
$"👤 <b>{employee.FullName}</b>\n"
|
||
+ $"🏢 Отдел: {employee.Department}\n"
|
||
+ $"🎓 Должность: {employee.Position}\n"
|
||
+ $"📸 Фото: <a href='{photoUrl}'>Посмотреть фото</a>\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<List<Employee>>(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;
|
||
}
|
||
}
|