feat(rendering): display description, system, duration, format, type and location in Telegram game card
This commit is contained in:
@@ -70,7 +70,17 @@ public sealed class CancelSessionHandler(
|
||||
|
||||
// 3. Загружаем весь батч для перерисовки
|
||||
var batchSessions = await connection.QueryAsync<SessionBatchDto>(
|
||||
@"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink, format as Format, location_address as LocationAddress
|
||||
@"SELECT id as SessionId,
|
||||
scheduled_at as ScheduledAt,
|
||||
status as Status,
|
||||
max_players as MaxPlayers,
|
||||
join_link as JoinLink,
|
||||
format as Format,
|
||||
location_address as LocationAddress,
|
||||
description as Description,
|
||||
system as System,
|
||||
duration_minutes as DurationMinutes,
|
||||
is_one_shot as IsOneShot
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at",
|
||||
|
||||
@@ -141,7 +141,11 @@ public sealed class PromoteWaitlistedPlayerHandler(
|
||||
max_players AS MaxPlayers,
|
||||
join_link AS JoinLink,
|
||||
format AS Format,
|
||||
location_address AS LocationAddress
|
||||
location_address AS LocationAddress,
|
||||
description AS Description,
|
||||
system AS System,
|
||||
duration_minutes AS DurationMinutes,
|
||||
is_one_shot AS IsOneShot
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ public sealed class RescheduleVotingDeadlineService(
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var batchSessions = (await connection.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress, description AS Description, system AS System, duration_minutes AS DurationMinutes, is_one_shot AS IsOneShot FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { result.BatchId })).ToList();
|
||||
|
||||
var batchParticipants = (await connection.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -17,22 +17,49 @@ public static class TelegramSessionBatchRenderer
|
||||
foreach (var session in view.Sessions)
|
||||
{
|
||||
messageText += $"📅 <b>{session.ScheduledAt.FormatMoscow()}</b>\n";
|
||||
messageText += session.MaxPlayers.HasValue
|
||||
? $"👥 Места: {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n"
|
||||
: $"👥 Игроки ({session.ActivePlayerCount}):\n";
|
||||
|
||||
if (!string.IsNullOrEmpty(session.JoinLink))
|
||||
var tags = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(session.System))
|
||||
tags.Add($"<b>Система:</b> {System.Net.WebUtility.HtmlEncode(session.System)}");
|
||||
if (!string.IsNullOrWhiteSpace(session.Format))
|
||||
tags.Add($"<b>Формат:</b> {System.Net.WebUtility.HtmlEncode(session.Format)}");
|
||||
tags.Add($"<b>Тип:</b> {(session.IsOneShot ? "One-shot" : "Кампания")}");
|
||||
|
||||
if (tags.Count > 0)
|
||||
{
|
||||
messageText += "🏷 " + string.Join(" · ", tags) + "\n";
|
||||
}
|
||||
|
||||
if (session.DurationMinutes.HasValue)
|
||||
{
|
||||
messageText += $"⏱ <b>Длительность:</b> {FormatDuration(session.DurationMinutes.Value)}\n";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(session.Description))
|
||||
{
|
||||
messageText += $"📝 <b>Описание:</b>\n{System.Net.WebUtility.HtmlEncode(session.Description)}\n\n";
|
||||
}
|
||||
|
||||
var format = session.Format ?? string.Empty;
|
||||
var isOnline = string.Equals(format, "Online", StringComparison.OrdinalIgnoreCase);
|
||||
var isOffline = string.Equals(format, "Offline", StringComparison.OrdinalIgnoreCase);
|
||||
var isHybrid = string.Equals(format, "Hybrid", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if ((isOnline || isHybrid) && !string.IsNullOrWhiteSpace(session.JoinLink))
|
||||
{
|
||||
var encodedLink = System.Net.WebUtility.HtmlEncode(session.JoinLink);
|
||||
messageText += $"🔗 Ссылка на игру: <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
messageText += $"🔗 <b>Ссылка:</b> <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
}
|
||||
|
||||
if (string.Equals(session.Format, "Offline", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
if ((isOffline || isHybrid) && !string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
{
|
||||
messageText += $"📍 Адрес: {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
messageText += $"📍 <b>Адрес:</b> {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
}
|
||||
|
||||
messageText += session.MaxPlayers.HasValue
|
||||
? $"👥 <b>Места:</b> {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n"
|
||||
: $"👥 <b>Игроки ({session.ActivePlayerCount}):</b>\n";
|
||||
|
||||
if (session.ActivePlayers.Count > 0)
|
||||
{
|
||||
messageText += string.Join("\n", session.ActivePlayers.Select(p =>
|
||||
@@ -45,7 +72,7 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
if (session.WaitlistedPlayers.Count > 0)
|
||||
{
|
||||
messageText += $"⏳ Лист ожидания ({session.WaitlistedPlayers.Count}):\n";
|
||||
messageText += $"⏳ <b>Лист ожидания ({session.WaitlistedPlayers.Count}):</b>\n";
|
||||
messageText += string.Join("\n", session.WaitlistedPlayers.Select(p =>
|
||||
$" ⏱ {(p.TelegramUsername != null ? "@" + p.TelegramUsername : p.DisplayName)}")) + "\n";
|
||||
}
|
||||
@@ -67,4 +94,14 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
return (messageText, new InlineKeyboardMarkup(buttons));
|
||||
}
|
||||
|
||||
private static string FormatDuration(int minutes)
|
||||
{
|
||||
if (minutes <= 0) return "0 мин";
|
||||
var hours = minutes / 60;
|
||||
var mins = minutes % 60;
|
||||
if (hours > 0 && mins > 0) return $"{hours} ч {mins} мин";
|
||||
if (hours > 0) return $"{hours} ч";
|
||||
return $"{mins} мин";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,11 @@ public sealed class CreateSessionHandler(
|
||||
command.MaxPlayers,
|
||||
command.Link,
|
||||
command.Format,
|
||||
command.LocationAddress));
|
||||
command.LocationAddress,
|
||||
command.Description,
|
||||
command.System?.ToString(),
|
||||
command.DurationMinutes,
|
||||
command.IsOneShot));
|
||||
}
|
||||
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
@@ -7,5 +7,9 @@ public sealed record SessionBatchDto(
|
||||
int? MaxPlayers,
|
||||
string JoinLink,
|
||||
string? Format = null,
|
||||
string? LocationAddress = null);
|
||||
string? LocationAddress = null,
|
||||
string? Description = null,
|
||||
string? System = null,
|
||||
int? DurationMinutes = null,
|
||||
bool IsOneShot = false);
|
||||
public sealed record ParticipantBatchDto(Guid SessionId, string DisplayName, string? TelegramUsername, string RegistrationStatus);
|
||||
|
||||
@@ -41,6 +41,10 @@ public static class SessionBatchViewBuilder
|
||||
session.JoinLink,
|
||||
session.Format,
|
||||
session.LocationAddress,
|
||||
session.Description,
|
||||
session.System,
|
||||
session.DurationMinutes,
|
||||
session.IsOneShot,
|
||||
activePlayers.Count,
|
||||
activePlayers,
|
||||
waitlistedPlayers,
|
||||
|
||||
@@ -14,6 +14,10 @@ public sealed record SessionViewItem(
|
||||
string JoinLink,
|
||||
string? Format,
|
||||
string? LocationAddress,
|
||||
string? Description,
|
||||
string? System,
|
||||
int? DurationMinutes,
|
||||
bool IsOneShot,
|
||||
int ActivePlayerCount,
|
||||
IReadOnlyList<PlayerViewItem> ActivePlayers,
|
||||
IReadOnlyList<PlayerViewItem> WaitlistedPlayers,
|
||||
|
||||
@@ -119,7 +119,14 @@ internal sealed record WebBatchSessionRow(
|
||||
long TelegramChatId,
|
||||
int? ThreadId,
|
||||
string NotificationMode,
|
||||
bool TopicCreatedByBot = false);
|
||||
bool TopicCreatedByBot = false,
|
||||
string? Description = null,
|
||||
string? System = null,
|
||||
int? DurationMinutes = null,
|
||||
string? Format = null,
|
||||
string? LocationAddress = null,
|
||||
bool IsOneShot = false,
|
||||
string? CoverImageUrl = null);
|
||||
internal sealed record WebTemplateGroupDto(long TelegramChatId);
|
||||
internal sealed record WebTemplateTopicDestination(int? MessageThreadId, bool TopicCreatedByBot);
|
||||
internal sealed record WebPublicGroupRow(
|
||||
@@ -1508,7 +1515,14 @@ public sealed class SessionService(
|
||||
g.external_group_id::BIGINT AS TelegramChatId,
|
||||
s.thread_id AS ThreadId,
|
||||
s.topic_created_by_bot AS TopicCreatedByBot,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.description AS Description,
|
||||
s.system AS System,
|
||||
s.duration_minutes AS DurationMinutes,
|
||||
s.format AS Format,
|
||||
s.location_address AS LocationAddress,
|
||||
s.is_one_shot AS IsOneShot,
|
||||
s.cover_image_url AS CoverImageUrl
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.batch_id = @BatchId
|
||||
@@ -1536,8 +1550,14 @@ public sealed class SessionService(
|
||||
var scheduledAt = BatchSchedulePlanner.ShiftForClone(sourceSession.ScheduledAt, interval);
|
||||
var sessionId = await conn.ExecuteScalarAsync<Guid>(
|
||||
"""
|
||||
INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, topic_created_by_bot, max_players, notification_mode)
|
||||
VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @TopicCreatedByBot, @MaxPlayers, @NotificationMode)
|
||||
INSERT INTO sessions (
|
||||
batch_id, group_id, title, join_link, scheduled_at, status, thread_id,
|
||||
topic_created_by_bot, max_players, notification_mode, description, system,
|
||||
duration_minutes, format, location_address, is_one_shot, cover_image_url)
|
||||
VALUES (
|
||||
@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId,
|
||||
@TopicCreatedByBot, @MaxPlayers, @NotificationMode, @Description, @System,
|
||||
@DurationMinutes, @Format, @LocationAddress, @IsOneShot, @CoverImageUrl)
|
||||
RETURNING id
|
||||
""",
|
||||
new
|
||||
@@ -1551,11 +1571,29 @@ public sealed class SessionService(
|
||||
ThreadId = threadId,
|
||||
sourceSession.TopicCreatedByBot,
|
||||
sourceSession.MaxPlayers,
|
||||
sourceSession.NotificationMode
|
||||
sourceSession.NotificationMode,
|
||||
Description = sourceSession.Description,
|
||||
System = sourceSession.System,
|
||||
DurationMinutes = sourceSession.DurationMinutes,
|
||||
Format = sourceSession.Format,
|
||||
LocationAddress = sourceSession.LocationAddress,
|
||||
IsOneShot = sourceSession.IsOneShot,
|
||||
CoverImageUrl = sourceSession.CoverImageUrl
|
||||
},
|
||||
transaction);
|
||||
|
||||
renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, sourceSession.MaxPlayers, batchJoinLink));
|
||||
renderedSessions.Add(new SessionBatchDto(
|
||||
sessionId,
|
||||
scheduledAt,
|
||||
SessionStatus.Planned,
|
||||
sourceSession.MaxPlayers,
|
||||
batchJoinLink,
|
||||
sourceSession.Format,
|
||||
sourceSession.LocationAddress,
|
||||
sourceSession.Description,
|
||||
sourceSession.System,
|
||||
sourceSession.DurationMinutes,
|
||||
sourceSession.IsOneShot));
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
@@ -1770,7 +1808,18 @@ public sealed class SessionService(
|
||||
},
|
||||
transaction);
|
||||
|
||||
renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, template.MaxPlayers, template.JoinLink));
|
||||
renderedSessions.Add(new SessionBatchDto(
|
||||
sessionId,
|
||||
scheduledAt,
|
||||
SessionStatus.Planned,
|
||||
template.MaxPlayers,
|
||||
template.JoinLink,
|
||||
Format: null,
|
||||
LocationAddress: null,
|
||||
Description: null,
|
||||
System: null,
|
||||
DurationMinutes: null,
|
||||
IsOneShot: false));
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
@@ -1897,7 +1946,7 @@ public sealed class SessionService(
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
|
||||
var sessions = (await conn.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress, description AS Description, system AS System, duration_minutes AS DurationMinutes, is_one_shot AS IsOneShot FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { BatchId = batchId })).ToList();
|
||||
|
||||
var participants = (await conn.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -16,22 +16,49 @@ public static class TelegramSessionBatchRenderer
|
||||
foreach (var session in view.Sessions)
|
||||
{
|
||||
messageText += $"📅 <b>{session.ScheduledAt.FormatMoscow()}</b>\n";
|
||||
messageText += session.MaxPlayers.HasValue
|
||||
? $"👥 Места: {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n"
|
||||
: $"👥 Игроки ({session.ActivePlayerCount}):\n";
|
||||
|
||||
if (!string.IsNullOrEmpty(session.JoinLink))
|
||||
var tags = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(session.System))
|
||||
tags.Add($"<b>Система:</b> {System.Net.WebUtility.HtmlEncode(session.System)}");
|
||||
if (!string.IsNullOrWhiteSpace(session.Format))
|
||||
tags.Add($"<b>Формат:</b> {System.Net.WebUtility.HtmlEncode(session.Format)}");
|
||||
tags.Add($"<b>Тип:</b> {(session.IsOneShot ? "One-shot" : "Кампания")}");
|
||||
|
||||
if (tags.Count > 0)
|
||||
{
|
||||
messageText += "🏷 " + string.Join(" · ", tags) + "\n";
|
||||
}
|
||||
|
||||
if (session.DurationMinutes.HasValue)
|
||||
{
|
||||
messageText += $"⏱ <b>Длительность:</b> {FormatDuration(session.DurationMinutes.Value)}\n";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(session.Description))
|
||||
{
|
||||
messageText += $"📝 <b>Описание:</b>\n{System.Net.WebUtility.HtmlEncode(session.Description)}\n\n";
|
||||
}
|
||||
|
||||
var format = session.Format ?? string.Empty;
|
||||
var isOnline = string.Equals(format, "Online", StringComparison.OrdinalIgnoreCase);
|
||||
var isOffline = string.Equals(format, "Offline", StringComparison.OrdinalIgnoreCase);
|
||||
var isHybrid = string.Equals(format, "Hybrid", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if ((isOnline || isHybrid) && !string.IsNullOrWhiteSpace(session.JoinLink))
|
||||
{
|
||||
var encodedLink = System.Net.WebUtility.HtmlEncode(session.JoinLink);
|
||||
messageText += $"🔗 Ссылка на игру: <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
messageText += $"🔗 <b>Ссылка:</b> <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
}
|
||||
|
||||
if (string.Equals(session.Format, "Offline", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
if ((isOffline || isHybrid) && !string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
{
|
||||
messageText += $"📍 Адрес: {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
messageText += $"📍 <b>Адрес:</b> {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
}
|
||||
|
||||
messageText += session.MaxPlayers.HasValue
|
||||
? $"👥 <b>Места:</b> {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n"
|
||||
: $"👥 <b>Игроки ({session.ActivePlayerCount}):</b>\n";
|
||||
|
||||
if (session.ActivePlayers.Count > 0)
|
||||
{
|
||||
messageText += string.Join("\n", session.ActivePlayers.Select(p =>
|
||||
@@ -44,7 +71,7 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
if (session.WaitlistedPlayers.Count > 0)
|
||||
{
|
||||
messageText += $"⏳ Лист ожидания ({session.WaitlistedPlayers.Count}):\n";
|
||||
messageText += $"⏳ <b>Лист ожидания ({session.WaitlistedPlayers.Count}):</b>\n";
|
||||
messageText += string.Join("\n", session.WaitlistedPlayers.Select(p =>
|
||||
$" ⏱ {(p.TelegramUsername != null ? "@" + p.TelegramUsername : p.DisplayName)}")) + "\n";
|
||||
}
|
||||
@@ -66,4 +93,14 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
return (messageText, new InlineKeyboardMarkup(buttons));
|
||||
}
|
||||
|
||||
private static string FormatDuration(int minutes)
|
||||
{
|
||||
if (minutes <= 0) return "0 мин";
|
||||
var hours = minutes / 60;
|
||||
var mins = minutes % 60;
|
||||
if (hours > 0 && mins > 0) return $"{hours} ч {mins} мин";
|
||||
if (hours > 0) return $"{hours} ч";
|
||||
return $"{mins} мин";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user