diff --git a/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs b/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs index db159f2..aa5b3f4 100644 --- a/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs +++ b/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs @@ -97,6 +97,35 @@ internal sealed class TelegramNotifier(ITelegramBotClient botClient, IIdentitySe } } + public async Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(options.Value.BotToken)) + return; + + var text = $"πŸ“° Новая Π½ΠΎΠ²ΠΎΡΡ‚ΡŒ: {Escape(title)}"; + + InlineKeyboardMarkup? keyboard = null; + if (!string.IsNullOrWhiteSpace(options.Value.PublicSiteUrl)) + { + var url = $"{options.Value.PublicSiteUrl.TrimEnd('/')}/news"; + keyboard = new InlineKeyboardMarkup(new[] { InlineKeyboardButton.WithUrl("🌐 ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ Π½Π° сайтС", url) }); + } + + var telegramUserIds = await identityService.GetActivatedLinkedTelegramUserIdsAsync(cancellationToken); + foreach (var telegramUserId in telegramUserIds) + { + try + { + await botClient.SendMessage( + telegramUserId, text, parseMode: ParseMode.Html, replyMarkup: keyboard, cancellationToken: cancellationToken); + } + catch + { + // ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ ΠΌΠΎΠ³ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΡ‚Π° β€” Π½Π΅ ΠΊΡ€ΠΈΡ‚ΠΈΡ‡Π½ΠΎ, ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ рассылку ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹ΠΌ. + } + } + } + public async Task NotifyUserAsync(Guid userId, string message, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(options.Value.BotToken)) diff --git a/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs b/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs index 4d6f73a..d6dee7d 100644 --- a/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs +++ b/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs @@ -6,7 +6,7 @@ using PnvPanel.Domain.News; namespace PnvPanel.Application.Admin.News; -public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeNotifier notifier) +public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeNotifier notifier, ITelegramNotifier telegramNotifier) : ICommandHandler> { public async Task> Handle(CreatePostCommand command, CancellationToken cancellationToken) @@ -15,6 +15,7 @@ public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeN dbContext.NewsPosts.Add(post); await notifier.NotifyNewsPublishedAsync(post.Id, post.Title, post.CreatedAt, cancellationToken); + await telegramNotifier.NotifyUsersNewsPublishedAsync(post.Title, cancellationToken); return Result.Success(NewsPostDto.FromDomain(post)); } diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs b/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs index 6bd0c63..14ad92e 100644 --- a/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs +++ b/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs @@ -60,4 +60,8 @@ public interface IIdentityService Task FindUserIdByTelegramUserIdAsync(long telegramUserId, CancellationToken cancellationToken); Task GetTelegramLinkInfoAsync(Guid userId, CancellationToken cancellationToken); + + /// Telegram ID Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… (Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Ρ…, Π½Π΅ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Ρ…) ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ с привязкой β€” + /// для рассылки ΡƒΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ Π²Ρ€ΠΎΠ΄Π΅ ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ новости. + Task> GetActivatedLinkedTelegramUserIdsAsync(CancellationToken cancellationToken); } diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs b/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs index 372d49e..84437df 100644 --- a/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs +++ b/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs @@ -21,4 +21,8 @@ public interface ITelegramNotifier /// Заявка Π½Π° Ρ€ΠΎΠ»ΡŒ β€” ΠΈΠ½Π»Π°ΠΉΠ½-ΠΊΠ½ΠΎΠΏΠΊΠΈ Β«ΠžΠ΄ΠΎΠ±Ρ€ΠΈΡ‚ΡŒ/ΠžΡ‚ΠΊΠ»ΠΎΠ½ΠΈΡ‚ΡŒΒ», Ρ€Π΅ΡˆΠ°Π΅Ρ‚ΡΡ ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π² Telegram. Task NotifyAdminsRoleRequestCreatedAsync( Guid ticketId, string userName, string roleDescription, string justification, CancellationToken cancellationToken); + + /// Рассылка ΠΎ ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ новости всСм Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΌ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡΠΌ с привязанным Telegram + /// (ΠΊΠ½ΠΎΠΏΠΊΠ°-ссылка Π½Π° сайт, Π³Π΄Π΅ Π½ΠΎΠ²ΠΎΡΡ‚ΡŒ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° Ρ†Π΅Π»ΠΈΠΊΠΎΠΌ). + Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken); } diff --git a/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs b/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs index df29b4e..7c78afe 100644 --- a/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs +++ b/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs @@ -252,6 +252,14 @@ internal sealed class IdentityService(UserManager userManager, SignInMa : new TelegramLinkInfo(false, null, null); } + public async Task> GetActivatedLinkedTelegramUserIdsAsync(CancellationToken cancellationToken) + { + return await userManager.Users.AsNoTracking() + .Where(u => u.TelegramUserId != null && u.IsActivated && !u.IsBlocked) + .Select(u => u.TelegramUserId!.Value) + .ToListAsync(cancellationToken); + } + private async Task GetPrimaryRoleNameAsync(AppUser user) { var roles = await userManager.GetRolesAsync(user);