feat(#21): support selected telegram topics for schedules
PR Checks / test-and-build (pull_request) Failing after 3m18s
PR Checks / test-and-build (pull_request) Failing after 3m18s
Route new schedules to an existing forum topic when /newsession is sent inside one, create bot-owned topics only from the forum root, and keep group notifications/dashboard updates threaded to the stored topic. Persist topic ownership so deletion only removes empty bot-created topics, add topic routing tests and smoke coverage, and bump release metadata to 1.14.0.
This commit is contained in:
@@ -56,7 +56,7 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="nav-version">v1.13.0</div>
|
||||
<div class="nav-version">v1.14.0</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@@ -79,4 +79,4 @@
|
||||
|
||||
private void ToggleMenu() => isOpen = !isOpen;
|
||||
private void CloseMenu() => isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ public sealed record WebSession(
|
||||
int? MaxPlayers,
|
||||
int ActivePlayerCount,
|
||||
int WaitlistedPlayerCount,
|
||||
string NotificationMode = SessionNotificationModeExtensions.GroupAndDirectValue);
|
||||
string NotificationMode = SessionNotificationModeExtensions.GroupAndDirectValue,
|
||||
int? ThreadId = null);
|
||||
|
||||
public sealed record WebParticipant(
|
||||
Guid Id,
|
||||
@@ -73,7 +74,8 @@ internal sealed record WebBatchSessionRow(
|
||||
int? BatchMessageId,
|
||||
long TelegramChatId,
|
||||
int? ThreadId,
|
||||
string NotificationMode);
|
||||
string NotificationMode,
|
||||
bool TopicCreatedByBot = false);
|
||||
internal sealed record WebTemplateGroupDto(long TelegramChatId);
|
||||
|
||||
public sealed class SessionService(
|
||||
@@ -314,7 +316,8 @@ public sealed class SessionService(
|
||||
s.max_players AS MaxPlayers,
|
||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.thread_id AS ThreadId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
LEFT JOIN LATERAL (
|
||||
@@ -351,7 +354,8 @@ public sealed class SessionService(
|
||||
s.max_players AS MaxPlayers,
|
||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.thread_id AS ThreadId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
LEFT JOIN LATERAL (
|
||||
@@ -409,7 +413,8 @@ public sealed class SessionService(
|
||||
s.max_players AS MaxPlayers,
|
||||
0 AS ActivePlayerCount,
|
||||
0 AS WaitlistedPlayerCount,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.thread_id AS ThreadId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.id = @Id AND s.group_id = @GroupId",
|
||||
@@ -463,7 +468,11 @@ public sealed class SessionService(
|
||||
"\n" +
|
||||
$"👥 Мест: <b>{(maxPlayers.HasValue ? maxPlayers.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : "без лимита")}</b>";
|
||||
|
||||
await bot.SendMessage(oldSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
await bot.SendMessage(
|
||||
chatId: oldSession.TelegramChatId,
|
||||
messageThreadId: oldSession.ThreadId,
|
||||
text: notification,
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
var mode = SessionNotificationModeExtensions.FromDatabaseValue(oldSession.NotificationMode);
|
||||
if (mode.ShouldSendDirectMessages())
|
||||
@@ -490,7 +499,8 @@ public sealed class SessionService(
|
||||
s.max_players AS MaxPlayers,
|
||||
0 AS ActivePlayerCount,
|
||||
0 AS WaitlistedPlayerCount,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.thread_id AS ThreadId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.id = @SessionId AND s.group_id = @GroupId
|
||||
@@ -567,8 +577,9 @@ public sealed class SessionService(
|
||||
await transaction.CommitAsync();
|
||||
|
||||
await bot.SendMessage(
|
||||
session.TelegramChatId,
|
||||
$"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
chatId: session.TelegramChatId,
|
||||
messageThreadId: session.ThreadId,
|
||||
text: $"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
if (session.BatchMessageId.HasValue)
|
||||
@@ -612,7 +623,8 @@ public sealed class SessionService(
|
||||
s.max_players AS MaxPlayers,
|
||||
0 AS ActivePlayerCount,
|
||||
0 AS WaitlistedPlayerCount,
|
||||
s.notification_mode AS NotificationMode
|
||||
s.notification_mode AS NotificationMode,
|
||||
s.thread_id AS ThreadId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.id = @SessionId AND s.group_id = @GroupId
|
||||
@@ -697,15 +709,17 @@ public sealed class SessionService(
|
||||
await transaction.CommitAsync();
|
||||
|
||||
await bot.SendMessage(
|
||||
session.TelegramChatId,
|
||||
$"🚪 <b>{System.Net.WebUtility.HtmlEncode(participant.DisplayName)}</b> удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
chatId: session.TelegramChatId,
|
||||
messageThreadId: session.ThreadId,
|
||||
text: $"🚪 <b>{System.Net.WebUtility.HtmlEncode(participant.DisplayName)}</b> удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
if (promoted is not null)
|
||||
{
|
||||
await bot.SendMessage(
|
||||
session.TelegramChatId,
|
||||
$"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
chatId: session.TelegramChatId,
|
||||
messageThreadId: session.ThreadId,
|
||||
text: $"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
if (session.BatchMessageId.HasValue)
|
||||
@@ -813,6 +827,7 @@ public sealed class SessionService(
|
||||
s.batch_message_id AS BatchMessageId,
|
||||
g.telegram_chat_id AS TelegramChatId,
|
||||
s.thread_id AS ThreadId,
|
||||
s.topic_created_by_bot AS TopicCreatedByBot,
|
||||
s.notification_mode AS NotificationMode
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
@@ -865,7 +880,11 @@ public sealed class SessionService(
|
||||
$"🗓 Новое начало: <b>{firstScheduledAt.FormatMoscow()}</b> (МСК)\n" +
|
||||
$"↔️ Шаг: <b>{intervalDays} дн.</b>";
|
||||
|
||||
await bot.SendMessage(firstSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
await bot.SendMessage(
|
||||
chatId: firstSession.TelegramChatId,
|
||||
messageThreadId: firstSession.ThreadId,
|
||||
text: notification,
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
var mode = SessionNotificationModeExtensions.FromDatabaseValue(firstSession.NotificationMode);
|
||||
if (mode.ShouldSendDirectMessages())
|
||||
@@ -892,6 +911,7 @@ public sealed class SessionService(
|
||||
s.batch_message_id AS BatchMessageId,
|
||||
g.telegram_chat_id AS TelegramChatId,
|
||||
s.thread_id AS ThreadId,
|
||||
s.topic_created_by_bot AS TopicCreatedByBot,
|
||||
s.notification_mode AS NotificationMode
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
@@ -920,8 +940,8 @@ 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, max_players, notification_mode)
|
||||
VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @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)
|
||||
VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @TopicCreatedByBot, @MaxPlayers, @NotificationMode)
|
||||
RETURNING id
|
||||
""",
|
||||
new
|
||||
@@ -933,6 +953,7 @@ public sealed class SessionService(
|
||||
ScheduledAt = scheduledAt,
|
||||
Status = SessionStatus.Planned,
|
||||
ThreadId = threadId,
|
||||
sourceSession.TopicCreatedByBot,
|
||||
sourceSession.MaxPlayers,
|
||||
sourceSession.NotificationMode
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user