using System.Text; using Dapper; using GmRelay.Shared.Domain; using Npgsql; using Telegram.Bot; using Telegram.Bot.Types; namespace GmRelay.Bot.Features.Sessions.ExportCalendar; internal sealed record CalendarSessionDto(Guid Id, string Title, DateTime ScheduledAt); public sealed class ExportCalendarHandler( NpgsqlDataSource dataSource, ITelegramBotClient botClient) { public async Task HandleAsync(Message message, CancellationToken cancellationToken) { await using var connection = await dataSource.OpenConnectionAsync(cancellationToken); var sessions = await connection.QueryAsync( @"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt FROM sessions s JOIN game_groups g ON s.group_id = g.id WHERE g.telegram_chat_id = @ChatId AND s.status = @Planned AND s.scheduled_at > NOW() ORDER BY s.scheduled_at ASC", new { ChatId = message.Chat.Id, Planned = SessionStatus.Planned }); var sessionsList = sessions.ToList(); if (sessionsList.Count == 0) { await botClient.SendMessage( chatId: message.Chat.Id, text: "📭 У этой группы нет запланированных сессий для экспорта.", cancellationToken: cancellationToken); return; } var sb = new StringBuilder(); sb.AppendLine("BEGIN:VCALENDAR"); sb.AppendLine("VERSION:2.0"); sb.AppendLine("PRODID:-//GM-Relay//TTRPG Schedule//EN"); foreach (var s in sessionsList) { var dtStart = s.ScheduledAt.ToString("yyyyMMddTHHmmssZ"); var dtEnd = s.ScheduledAt.AddHours(4).ToString("yyyyMMddTHHmmssZ"); sb.AppendLine("BEGIN:VEVENT"); sb.AppendLine($"UID:{s.Id}@gmrelay"); sb.AppendLine($"DTSTAMP:{DateTime.UtcNow:yyyyMMddTHHmmssZ}"); sb.AppendLine($"DTSTART:{dtStart}"); sb.AppendLine($"DTEND:{dtEnd}"); sb.AppendLine($"SUMMARY:{s.Title}"); // Escape special chars according to iCal standards (RFC 5545) -- simple escaping for summary // In a fuller implementation we'd escape \r\n, commas, etc. But titles are mostly plain text. sb.AppendLine("END:VEVENT"); } sb.AppendLine("END:VCALENDAR"); var bytes = Encoding.UTF8.GetBytes(sb.ToString()); using var stream = new MemoryStream(bytes); var inputFile = InputFile.FromStream(stream, "schedule.ics"); await botClient.SendDocument( chatId: message.Chat.Id, document: inputFile, caption: "📅 Ваш календарь игр!\nОткройте файл на устройстве, чтобы добавить события в свой календарь.", parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, messageThreadId: message.MessageThreadId, cancellationToken: cancellationToken); } }