feat: add campaign templates and recurring schedules
Deploy Telegram Bot / build-and-push (push) Successful in 3m49s
Deploy Telegram Bot / deploy (push) Successful in 10s

This commit is contained in:
2026-04-28 10:01:18 +03:00
parent a1ec688ec8
commit 0218890a7a
17 changed files with 1075 additions and 14 deletions
@@ -9,17 +9,21 @@ internal sealed record NewSessionParseResult(
IReadOnlyList<DateTimeOffset> ScheduledTimes,
IReadOnlyList<string> PastTimeInputs,
IReadOnlyList<string> InvalidTimeInputs,
IReadOnlyList<string> InvalidSeatLimitInputs)
IReadOnlyList<string> InvalidSeatLimitInputs,
IReadOnlyList<string> InvalidRecurringInputs)
{
public bool IsValid =>
!string.IsNullOrWhiteSpace(Title) &&
!string.IsNullOrWhiteSpace(Link) &&
ScheduledTimes.Count > 0 &&
InvalidSeatLimitInputs.Count == 0;
InvalidSeatLimitInputs.Count == 0 &&
InvalidRecurringInputs.Count == 0;
}
internal static class NewSessionCommandParser
{
private const int MaxRecurringSessionCount = 52;
private const int MaxRecurringIntervalDays = 365;
private const string TitlePrefix = "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435:";
private const string TimePrefix = "\u0412\u0440\u0435\u043c\u044f:";
private const string LinkPrefix = "\u0421\u0441\u044b\u043b\u043a\u0430:";
@@ -29,16 +33,30 @@ internal static class NewSessionCommandParser
"\u041b\u0438\u043c\u0438\u0442:",
"\u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c:"
];
private static readonly string[] RecurringCountPrefixes =
[
"\u0418\u0433\u0440:",
"\u0421\u0435\u0441\u0441\u0438\u0439:",
"\u041f\u043e\u0432\u0442\u043e\u0440\u043e\u0432:"
];
private static readonly string[] RecurringIntervalPrefixes =
[
"\u0428\u0430\u0433:",
"\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b:"
];
public static NewSessionParseResult Parse(string? text, DateTimeOffset nowUtc)
{
string? title = null;
string? link = null;
int? maxPlayers = null;
int? recurringCount = null;
var recurringIntervalDays = 7;
var scheduledTimes = new List<DateTimeOffset>();
var pastTimeInputs = new List<string>();
var invalidTimeInputs = new List<string>();
var invalidSeatLimitInputs = new List<string>();
var invalidRecurringInputs = new List<string>();
foreach (var line in (text ?? string.Empty).Split('\n', StringSplitOptions.TrimEntries))
{
@@ -71,6 +89,42 @@ internal static class NewSessionCommandParser
continue;
}
var recurringCountPrefix = RecurringCountPrefixes.FirstOrDefault(prefix =>
line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
if (recurringCountPrefix is not null)
{
var recurringInput = line[recurringCountPrefix.Length..].Trim();
if (int.TryParse(recurringInput, out var parsedCount) &&
parsedCount is >= 1 and <= MaxRecurringSessionCount)
{
recurringCount = parsedCount;
}
else
{
invalidRecurringInputs.Add(recurringInput);
}
continue;
}
var recurringIntervalPrefix = RecurringIntervalPrefixes.FirstOrDefault(prefix =>
line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
if (recurringIntervalPrefix is not null)
{
var recurringInput = line[recurringIntervalPrefix.Length..].Trim();
if (int.TryParse(recurringInput, out var parsedInterval) &&
parsedInterval is >= 1 and <= MaxRecurringIntervalDays)
{
recurringIntervalDays = parsedInterval;
}
else
{
invalidRecurringInputs.Add(recurringInput);
}
continue;
}
if (!line.StartsWith(TimePrefix, StringComparison.OrdinalIgnoreCase))
{
continue;
@@ -92,6 +146,14 @@ internal static class NewSessionCommandParser
scheduledTimes.Add(scheduledAt);
}
if (recurringCount.HasValue && scheduledTimes.Count == 1)
{
var firstScheduledTime = scheduledTimes[0];
scheduledTimes = Enumerable.Range(0, recurringCount.Value)
.Select(index => firstScheduledTime.AddDays(recurringIntervalDays * index))
.ToList();
}
return new NewSessionParseResult(
title,
link,
@@ -99,6 +161,7 @@ internal static class NewSessionCommandParser
scheduledTimes,
pastTimeInputs,
invalidTimeInputs,
invalidSeatLimitInputs);
invalidSeatLimitInputs,
invalidRecurringInputs);
}
}