Enhance TelegramNotifier to include channel names in notifications
Updated the TelegramNotifier to fetch channel names in a single database query, improving efficiency. Modified notification messages to include channel names for better clarity, ensuring users know where to switch for live content. Added tests to verify the inclusion of channel names in notifications and updated documentation to reflect these changes.
This commit is contained in:
@@ -37,20 +37,36 @@ public sealed class TelegramNotifier(IAppDbContext dbContext, ITelegramApi api)
|
|||||||
? marker
|
? marker
|
||||||
: now - MaxCatchUp;
|
: now - MaxCatchUp;
|
||||||
|
|
||||||
|
// Имена каналов — одним запросом на тик: их десятки, а оповещений за тик единицы, и ходить
|
||||||
|
// за названием по каждому значило бы столько же одинаковых запросов.
|
||||||
|
var channels = await dbContext
|
||||||
|
.Channels.AsNoTracking()
|
||||||
|
.ToDictionaryAsync(c => c.Id, c => c.Name, cancellationToken);
|
||||||
|
|
||||||
var sent = 0;
|
var sent = 0;
|
||||||
sent += await NotifyStartedAsync(settings, from, now, cancellationToken);
|
sent += await NotifyStartedAsync(settings, channels, from, now, cancellationToken);
|
||||||
sent += await NotifyUpNextAsync(settings, from, now, cancellationToken);
|
sent += await NotifyUpNextAsync(settings, channels, from, now, cancellationToken);
|
||||||
|
|
||||||
settings.MarkNotifiedUntil(now);
|
settings.MarkNotifiedUntil(now);
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подпись канала под заголовком программы. Каналов у зрителя несколько, а «Сейчас в эфире»
|
||||||
|
/// без названия не отвечает на вопрос «где» — по такому сообщению непонятно, куда переключаться.
|
||||||
|
/// </summary>
|
||||||
|
private static string ChannelLine(IReadOnlyDictionary<Guid, string> channels, Guid channelId) =>
|
||||||
|
channels.GetValueOrDefault(channelId) is { } name
|
||||||
|
? $" · {TelegramText.Escape(name)}"
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Начавшиеся программы. Оповещаем о смене шоу, а не о каждой записи: четыре серии подряд —
|
/// Начавшиеся программы. Оповещаем о смене шоу, а не о каждой записи: четыре серии подряд —
|
||||||
/// это одна программа в глазах зрителя, и четыре сообщения о ней читаются как спам.
|
/// это одна программа в глазах зрителя, и четыре сообщения о ней читаются как спам.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<int> NotifyStartedAsync(
|
private async Task<int> NotifyStartedAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
|
IReadOnlyDictionary<Guid, string> channels,
|
||||||
DateTimeOffset from,
|
DateTimeOffset from,
|
||||||
DateTimeOffset now,
|
DateTimeOffset now,
|
||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
@@ -81,7 +97,7 @@ public sealed class TelegramNotifier(IAppDbContext dbContext, ITelegramApi api)
|
|||||||
settings,
|
settings,
|
||||||
entry.ChannelId,
|
entry.ChannelId,
|
||||||
TelegramNotificationKind.ShowStart,
|
TelegramNotificationKind.ShowStart,
|
||||||
$"▶️ <b>{TelegramText.Escape(title)}</b>\nСейчас в эфире",
|
$"▶️ <b>{TelegramText.Escape(title)}</b>\nСейчас в эфире{ChannelLine(channels, entry.ChannelId)}",
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,6 +108,7 @@ public sealed class TelegramNotifier(IAppDbContext dbContext, ITelegramApi api)
|
|||||||
/// <summary>Что начнётся через <see cref="UpNextLead"/> — предупреждаем один раз, в своё окно.</summary>
|
/// <summary>Что начнётся через <see cref="UpNextLead"/> — предупреждаем один раз, в своё окно.</summary>
|
||||||
private async Task<int> NotifyUpNextAsync(
|
private async Task<int> NotifyUpNextAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
|
IReadOnlyDictionary<Guid, string> channels,
|
||||||
DateTimeOffset from,
|
DateTimeOffset from,
|
||||||
DateTimeOffset now,
|
DateTimeOffset now,
|
||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
@@ -123,7 +140,7 @@ public sealed class TelegramNotifier(IAppDbContext dbContext, ITelegramApi api)
|
|||||||
settings,
|
settings,
|
||||||
entry.ChannelId,
|
entry.ChannelId,
|
||||||
TelegramNotificationKind.UpNext,
|
TelegramNotificationKind.UpNext,
|
||||||
$"🕔 Через {minutes} мин: <b>{TelegramText.Escape(title)}</b>",
|
$"🕔 <b>{TelegramText.Escape(title)}</b>\nЧерез {minutes} мин{ChannelLine(channels, entry.ChannelId)}",
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,9 +136,11 @@ public class TelegramNotifierTests
|
|||||||
.SendMessageAsync(
|
.SendMessageAsync(
|
||||||
Arg.Any<TelegramSettings>(),
|
Arg.Any<TelegramSettings>(),
|
||||||
100,
|
100,
|
||||||
|
// Канал назван в самом сообщении: их у зрителя несколько, и «сейчас в эфире»
|
||||||
|
// без названия не отвечает на вопрос, куда переключаться.
|
||||||
// Предикат NSubstitute принимает аргумент как допускающий null — само сообщение
|
// Предикат NSubstitute принимает аргумент как допускающий null — само сообщение
|
||||||
// объявлено обязательным, потому и разыменовываем без проверки.
|
// объявлено обязательным, потому и разыменовываем без проверки.
|
||||||
Arg.Is<string>(s => s!.Contains("Футурама")),
|
Arg.Is<string>(s => s!.Contains("Футурама") && s.Contains("Мультреалити")),
|
||||||
null,
|
null,
|
||||||
Arg.Any<CancellationToken>()
|
Arg.Any<CancellationToken>()
|
||||||
);
|
);
|
||||||
@@ -147,6 +149,50 @@ public class TelegramNotifierTests
|
|||||||
Assert.Equal(Now, settings.NotifiedUntil);
|
Assert.Equal(Now, settings.NotifiedUntil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Notifies_UpNext_TellsWhenAndWhere()
|
||||||
|
{
|
||||||
|
var fixture = new TestDb();
|
||||||
|
var channel = Channel.Create("Мультреалити", "mult", Now);
|
||||||
|
var soon = Show.Create("Гриффины", ShowKind.Series);
|
||||||
|
|
||||||
|
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
|
||||||
|
subscriber.Toggle(channel.Id, TelegramNotificationKind.UpNext);
|
||||||
|
|
||||||
|
await using (var seed = fixture.New())
|
||||||
|
{
|
||||||
|
seed.Channels.Add(channel);
|
||||||
|
seed.Shows.Add(soon);
|
||||||
|
seed.TelegramSubscribers.Add(subscriber);
|
||||||
|
seed.ScheduleEntries.Add(Program(channel.Id, soon.Id, Now.AddMinutes(4), 25));
|
||||||
|
await seed.SaveChangesAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = Substitute.For<ITelegramApi>();
|
||||||
|
var settings = Settings();
|
||||||
|
// Окно предупреждения — свои пять минут: разобранное кончается на Now, а смотрим до Now+5.
|
||||||
|
settings.MarkNotifiedUntil(Now.AddMinutes(-5));
|
||||||
|
|
||||||
|
await using var db = fixture.New();
|
||||||
|
var sent = await new TelegramNotifier(db, api).RunAsync(
|
||||||
|
settings,
|
||||||
|
Now,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(1, sent);
|
||||||
|
await api.Received(1)
|
||||||
|
.SendMessageAsync(
|
||||||
|
Arg.Any<TelegramSettings>(),
|
||||||
|
100,
|
||||||
|
Arg.Is<string>(s =>
|
||||||
|
s!.Contains("Гриффины") && s.Contains("4 мин") && s.Contains("Мультреалити")
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
Arg.Any<CancellationToken>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Notifies_ReplacingItsPreviousAlert()
|
public async Task Notifies_ReplacingItsPreviousAlert()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ NAT и через прокси, и это единственный вариан
|
|||||||
в глазах зрителя, и четыре сообщения о ней читаются как спам.
|
в глазах зрителя, и четыре сообщения о ней читаются как спам.
|
||||||
- **Скоро в эфире** — за пять минут до следующей программы.
|
- **Скоро в эфире** — за пять минут до следующей программы.
|
||||||
|
|
||||||
|
Оба называют канал: подписок у зрителя несколько, и «Сейчас в эфире» без названия не отвечает
|
||||||
|
на вопрос «где» — по такому сообщению непонятно, куда переключаться.
|
||||||
|
|
||||||
Граница разобранного (`TelegramSettings.NotifiedUntil`) двигается на каждом тике, даже когда
|
Граница разобранного (`TelegramSettings.NotifiedUntil`) двигается на каждом тике, даже когда
|
||||||
подписчиков нет: иначе первый же подписавшийся получил бы пачку сообщений про всё, что успело
|
подписчиков нет: иначе первый же подписавшийся получил бы пачку сообщений про всё, что успело
|
||||||
пройти. Догон ограничен пятнадцатью минутами — после долгого простоя бот не станет пересказывать
|
пройти. Догон ограничен пятнадцатью минутами — после долгого простоя бот не станет пересказывать
|
||||||
|
|||||||
Reference in New Issue
Block a user