Enhance Telegram bot functionality with message management and new API methods
Updated the ITelegramApi interface to include a method for deleting messages and modified SendMessageAsync to return the message ID. Enhanced the TelegramBotService to manage message visibility by replacing previous messages with new ones, ensuring a cleaner user experience. Introduced properties in TelegramSubscriber to track the latest menu and notice message IDs. Updated related tests to verify the new message management behavior and adjusted documentation to reflect these changes.
This commit is contained in:
@@ -40,7 +40,8 @@ public interface ITelegramApi
|
|||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
Task SendMessageAsync(
|
/// <summary>Отправляет сообщение и возвращает его идентификатор — по нему потом правят и удаляют.</summary>
|
||||||
|
Task<long?> SendMessageAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
long chatId,
|
long chatId,
|
||||||
string text,
|
string text,
|
||||||
@@ -58,6 +59,17 @@ public interface ITelegramApi
|
|||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Снимает своё сообщение. В личном чате бот вправе удалять только собственные — команды
|
||||||
|
/// зрителя остаются, и это не в нашей власти.
|
||||||
|
/// </summary>
|
||||||
|
Task DeleteMessageAsync(
|
||||||
|
TelegramSettings settings,
|
||||||
|
long chatId,
|
||||||
|
long messageId,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>Гасит «часики» на нажатой кнопке; текст — всплывающая подсказка.</summary>
|
/// <summary>Гасит «часики» на нажатой кнопке; текст — всплывающая подсказка.</summary>
|
||||||
Task AnswerCallbackAsync(
|
Task AnswerCallbackAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
|
|||||||
@@ -60,9 +60,13 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
// Любое другое сообщение — это «я не знаю, что нажать»: показываем меню, если чат известен.
|
// Любое другое сообщение — это «я не знаю, что нажать»: показываем меню, если чат известен.
|
||||||
var subscriber = await FindAsync(chatId, cancellationToken);
|
var subscriber = await FindAsync(chatId, cancellationToken);
|
||||||
if (subscriber is null)
|
if (subscriber is null)
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.NeedLink, null, cancellationToken);
|
{
|
||||||
else
|
await NoticeAsync(settings, null, chatId, Texts.NeedLink, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,20 +93,14 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
// Уже привязанный чат просто открывает меню: заставлять его снова ходить за кодом незачем.
|
// Уже привязанный чат просто открывает меню: заставлять его снова ходить за кодом незачем.
|
||||||
if (subscriber is null)
|
if (subscriber is null)
|
||||||
{
|
{
|
||||||
await api.SendMessageAsync(
|
await NoticeAsync(settings, null, chatId, Texts.NeedLink, cancellationToken);
|
||||||
settings,
|
|
||||||
chatId,
|
|
||||||
Texts.NeedLink,
|
|
||||||
null,
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriber.Resume();
|
subscriber.Resume();
|
||||||
subscriber.Touch(now);
|
subscriber.Touch(now);
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
|
||||||
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +110,7 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
);
|
);
|
||||||
if (link is null || !link.IsUsable(now))
|
if (link is null || !link.IsUsable(now))
|
||||||
{
|
{
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.BadCode, null, cancellationToken);
|
await NoticeAsync(settings, subscriber, chatId, Texts.BadCode, cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +126,9 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
subscriber.Rebind(link.UserId, from, now);
|
subscriber.Rebind(link.UserId, from, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await NoticeAsync(settings, subscriber, chatId, Texts.Linked, cancellationToken);
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.Linked, null, cancellationToken);
|
|
||||||
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
await ShowMenuAsync(settings, subscriber, chatId, null, cancellationToken);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleStopAsync(
|
private async Task HandleStopAsync(
|
||||||
@@ -145,8 +143,8 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
|
|
||||||
// Подписки не трогаем: вернувшись по /start, зритель получит свои каналы, а не пустой список.
|
// Подписки не трогаем: вернувшись по /start, зритель получит свои каналы, а не пустой список.
|
||||||
subscriber.Stop();
|
subscriber.Stop();
|
||||||
|
await NoticeAsync(settings, subscriber, chatId, Texts.Stopped, cancellationToken);
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.Stopped, null, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleCallbackAsync(
|
private async Task HandleCallbackAsync(
|
||||||
@@ -188,7 +186,8 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
subscriber.Touch(now);
|
subscriber.Touch(now);
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
await AnswerAsync(settings, update, null, cancellationToken);
|
await AnswerAsync(settings, update, null, cancellationToken);
|
||||||
await SendScheduleAsync(settings, channel, chatId, now, cancellationToken);
|
await SendScheduleAsync(settings, subscriber, channel, chatId, now, cancellationToken);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +219,28 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
update.CallbackMessageId,
|
update.CallbackMessageId,
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Одноразовое сообщение бота: программа передач, подтверждение привязки, отказ. Предыдущее
|
||||||
|
/// такое же снимается — три программы на разное время в чате не нужны никому.
|
||||||
|
/// </summary>
|
||||||
|
private async Task NoticeAsync(
|
||||||
|
TelegramSettings settings,
|
||||||
|
TelegramSubscriber? subscriber,
|
||||||
|
long chatId,
|
||||||
|
string text,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (subscriber?.NoticeMessageId is { } previous)
|
||||||
|
await api.DeleteMessageAsync(settings, chatId, previous, cancellationToken);
|
||||||
|
|
||||||
|
var messageId = await api.SendMessageAsync(settings, chatId, text, null, cancellationToken);
|
||||||
|
|
||||||
|
// Незнакомому чату помнить нечего: подписчика ещё нет, и следующее сообщение будет первым.
|
||||||
|
subscriber?.SetNoticeMessage(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task AnswerAsync(
|
private Task AnswerAsync(
|
||||||
@@ -254,7 +275,7 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
|
|
||||||
if (channels.Count == 0)
|
if (channels.Count == 0)
|
||||||
{
|
{
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.NoChannels, null, cancellationToken);
|
await NoticeAsync(settings, subscriber, chatId, Texts.NoChannels, cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +294,10 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Нажали кнопку — правим то же сообщение. Пришли заново — старое меню снимаем: две панели
|
||||||
|
// с разными галочками в одном чате означают, что одна из них врёт.
|
||||||
if (messageId is { } id)
|
if (messageId is { } id)
|
||||||
|
{
|
||||||
await api.EditMessageAsync(
|
await api.EditMessageAsync(
|
||||||
settings,
|
settings,
|
||||||
chatId,
|
chatId,
|
||||||
@@ -282,8 +306,21 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
keyboard,
|
keyboard,
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
else
|
subscriber.SetMenuMessage(id);
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.Menu, keyboard, cancellationToken);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriber.MenuMessageId is { } previous)
|
||||||
|
await api.DeleteMessageAsync(settings, chatId, previous, cancellationToken);
|
||||||
|
|
||||||
|
var sent = await api.SendMessageAsync(
|
||||||
|
settings,
|
||||||
|
chatId,
|
||||||
|
Texts.Menu,
|
||||||
|
keyboard,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
subscriber.SetMenuMessage(sent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TelegramButton Button(
|
private static TelegramButton Button(
|
||||||
@@ -310,6 +347,7 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task SendScheduleAsync(
|
private async Task SendScheduleAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
|
TelegramSubscriber subscriber,
|
||||||
Channel channel,
|
Channel channel,
|
||||||
long chatId,
|
long chatId,
|
||||||
DateTimeOffset now,
|
DateTimeOffset now,
|
||||||
@@ -340,7 +378,7 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
|
|
||||||
if (entries.Count == 0)
|
if (entries.Count == 0)
|
||||||
{
|
{
|
||||||
await api.SendMessageAsync(settings, chatId, Texts.NoSchedule, null, cancellationToken);
|
await NoticeAsync(settings, subscriber, chatId, Texts.NoSchedule, cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,6 +434,9 @@ public sealed class TelegramBotService(IAppDbContext dbContext, ITelegramApi api
|
|||||||
|
|
||||||
public const string Menu =
|
public const string Menu =
|
||||||
"<b>Оповещения</b>\nНажмите, чтобы включить или выключить. ✅ — включено.\n"
|
"<b>Оповещения</b>\nНажмите, чтобы включить или выключить. ✅ — включено.\n"
|
||||||
|
// Кнопка-заголовок ничем не выдаёт, что она кнопка: про программу передач надо сказать
|
||||||
|
// словами, иначе её найдут только промахнувшись мимо тумблера.
|
||||||
|
+ "Программа передач — нажмите на название канала.\n"
|
||||||
+ "Остановить всё — командой /stop.";
|
+ "Остановить всё — командой /stop.";
|
||||||
|
|
||||||
public const string NoChannels = "Пока нет ни одного включённого канала.";
|
public const string NoChannels = "Пока нет ни одного включённого канала.";
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ public class TelegramSubscriber
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsStopped { get; private set; }
|
public bool IsStopped { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Последнее меню подписок в чате. Бот убирает за собой: при новом <c>/start</c> старое меню
|
||||||
|
/// снимается, иначе чат превращается в ленту одинаковых панелей, и непонятно, какая из них
|
||||||
|
/// показывает правду.
|
||||||
|
/// </summary>
|
||||||
|
public long? MenuMessageId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Последнее одноразовое сообщение бота — программа передач, подтверждение привязки. Живёт
|
||||||
|
/// до следующего такого же: держать в чате три программы на разное время бессмысленно.
|
||||||
|
/// </summary>
|
||||||
|
public long? NoticeMessageId { get; private set; }
|
||||||
|
|
||||||
public IReadOnlyList<TelegramSubscription> Subscriptions => _subscriptions;
|
public IReadOnlyList<TelegramSubscription> Subscriptions => _subscriptions;
|
||||||
|
|
||||||
private TelegramSubscriber() { }
|
private TelegramSubscriber() { }
|
||||||
@@ -64,6 +77,11 @@ public class TelegramSubscriber
|
|||||||
|
|
||||||
public void Touch(DateTimeOffset now) => LastSeenAt = now;
|
public void Touch(DateTimeOffset now) => LastSeenAt = now;
|
||||||
|
|
||||||
|
/// <summary>Запоминает, каким сообщением сейчас показано меню (null — его в чате нет).</summary>
|
||||||
|
public void SetMenuMessage(long? messageId) => MenuMessageId = messageId;
|
||||||
|
|
||||||
|
public void SetNoticeMessage(long? messageId) => NoticeMessageId = messageId;
|
||||||
|
|
||||||
public void Stop() => IsStopped = true;
|
public void Stop() => IsStopped = true;
|
||||||
|
|
||||||
public void Resume() => IsStopped = false;
|
public void Resume() => IsStopped = false;
|
||||||
|
|||||||
Generated
+1599
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TeleWave.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class TelegramBotMessages : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "MenuMessageId",
|
||||||
|
table: "TelegramSubscribers",
|
||||||
|
type: "bigint",
|
||||||
|
nullable: true
|
||||||
|
);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "NoticeMessageId",
|
||||||
|
table: "TelegramSubscribers",
|
||||||
|
type: "bigint",
|
||||||
|
nullable: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(name: "MenuMessageId", table: "TelegramSubscribers");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(name: "NoticeMessageId", table: "TelegramSubscribers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -850,6 +850,12 @@ namespace TeleWave.Infrastructure.Migrations
|
|||||||
b.Property<DateTimeOffset?>("LastSeenAt")
|
b.Property<DateTimeOffset?>("LastSeenAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long?>("MenuMessageId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("NoticeMessageId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
b.Property<Guid>("UserId")
|
b.Property<Guid>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
|||||||
@@ -65,14 +65,15 @@ public sealed class TelegramApi(ILogger<TelegramApi> logger) : ITelegramApi
|
|||||||
return result?.Result?.Select(Map).ToList() ?? [];
|
return result?.Result?.Select(Map).ToList() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendMessageAsync(
|
public async Task<long?> SendMessageAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
long chatId,
|
long chatId,
|
||||||
string text,
|
string text,
|
||||||
IReadOnlyList<IReadOnlyList<TelegramButton>>? keyboard,
|
IReadOnlyList<IReadOnlyList<TelegramButton>>? keyboard,
|
||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
) =>
|
)
|
||||||
CallAsync<OkResult>(
|
{
|
||||||
|
var result = await CallAsync<MessageResult>(
|
||||||
settings,
|
settings,
|
||||||
"sendMessage",
|
"sendMessage",
|
||||||
new
|
new
|
||||||
@@ -85,6 +86,33 @@ public sealed class TelegramApi(ILogger<TelegramApi> logger) : ITelegramApi
|
|||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return result?.Result?.MessageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteMessageAsync(
|
||||||
|
TelegramSettings settings,
|
||||||
|
long chatId,
|
||||||
|
long messageId,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CallAsync<OkResult>(
|
||||||
|
settings,
|
||||||
|
"deleteMessage",
|
||||||
|
new { chat_id = chatId, message_id = messageId },
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (TelegramApiException exception)
|
||||||
|
{
|
||||||
|
// Сообщение мог удалить сам зритель, или ему больше 48 часов — Telegram столько
|
||||||
|
// и хранит право на удаление. Уборка не та задача, ради которой стоит рвать разговор.
|
||||||
|
logger.LogDebug(exception, "Не удалось снять сообщение {MessageId}", messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task EditMessageAsync(
|
public Task EditMessageAsync(
|
||||||
TelegramSettings settings,
|
TelegramSettings settings,
|
||||||
long chatId,
|
long chatId,
|
||||||
@@ -269,6 +297,8 @@ public sealed class TelegramApi(ILogger<TelegramApi> logger) : ITelegramApi
|
|||||||
|
|
||||||
private sealed record OkResult(bool Ok);
|
private sealed record OkResult(bool Ok);
|
||||||
|
|
||||||
|
private sealed record MessageResult(bool Ok, MessageDto? Result);
|
||||||
|
|
||||||
private sealed record MeResult(bool Ok, MeDto? Result);
|
private sealed record MeResult(bool Ok, MeDto? Result);
|
||||||
|
|
||||||
private sealed record MeDto(long Id, string Username);
|
private sealed record MeDto(long Id, string Username);
|
||||||
|
|||||||
@@ -235,6 +235,67 @@ public class TelegramBotTests
|
|||||||
Assert.Equal(1, sent!.Split("Симпсоны").Length - 1);
|
Assert.Equal(1, sent!.Split("Симпсоны").Length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Menu_ReplacesThePreviousOne()
|
||||||
|
{
|
||||||
|
// Два меню в чате — это две панели с разными галочками, и одна из них врёт. Старое снимаем.
|
||||||
|
var fixture = new TestDb();
|
||||||
|
var channel = Channel.Create("Мультреалити", "mult", Now);
|
||||||
|
channel.UpdateSettings(channel.Name, isEnabled: true, null);
|
||||||
|
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
|
||||||
|
|
||||||
|
await using (var seed = fixture.New())
|
||||||
|
{
|
||||||
|
seed.Channels.Add(channel);
|
||||||
|
seed.TelegramSubscribers.Add(subscriber);
|
||||||
|
await seed.SaveChangesAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = Substitute.For<ITelegramApi>();
|
||||||
|
api.SendMessageAsync(
|
||||||
|
Arg.Any<TelegramSettings>(),
|
||||||
|
Arg.Any<long>(),
|
||||||
|
Arg.Any<string>(),
|
||||||
|
Arg.Any<IReadOnlyList<IReadOnlyList<TelegramButton>>?>(),
|
||||||
|
Arg.Any<CancellationToken>()
|
||||||
|
)
|
||||||
|
.Returns(777L);
|
||||||
|
|
||||||
|
await using (var db = fixture.New())
|
||||||
|
{
|
||||||
|
await new TelegramBotService(db, api).HandleAsync(
|
||||||
|
Settings(),
|
||||||
|
Message(100, "/start"),
|
||||||
|
Now,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var check = fixture.New())
|
||||||
|
{
|
||||||
|
Assert.Equal(777L, check.TelegramSubscribers.Single().MenuMessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var db = fixture.New())
|
||||||
|
{
|
||||||
|
await new TelegramBotService(db, api).HandleAsync(
|
||||||
|
Settings(),
|
||||||
|
Message(100, "/start"),
|
||||||
|
Now,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Второй заход снял предыдущее меню, а не оставил его висеть.
|
||||||
|
await api.Received(1)
|
||||||
|
.DeleteMessageAsync(
|
||||||
|
Arg.Any<TelegramSettings>(),
|
||||||
|
100,
|
||||||
|
777L,
|
||||||
|
Arg.Any<CancellationToken>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Callback_FromUnknownChat_IsRefused()
|
public async Task Callback_FromUnknownChat_IsRefused()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ NAT и через прокси, и это единственный вариан
|
|||||||
нажатие снимает подписку. `/stop` останавливает рассылку, не стирая подписки: вернувшись, зритель
|
нажатие снимает подписку. `/stop` останавливает рассылку, не стирая подписки: вернувшись, зритель
|
||||||
получит свои каналы, а не пустой список.
|
получит свои каналы, а не пустой список.
|
||||||
|
|
||||||
|
**Бот убирает за собой.** В чате живут ровно два его сообщения: меню подписок и последнее
|
||||||
|
одноразовое — программа передач, подтверждение привязки или отказ. Новое меню снимает предыдущее
|
||||||
|
(две панели с разными галочками означают, что одна врёт), новая программа — предыдущую программу.
|
||||||
|
Идентификаторы обоих сообщений хранятся у подписчика. Команды зрителя остаются: в личном чате бот
|
||||||
|
вправе удалять только собственные сообщения, и права на чужие у него нет и не будет. Не удалившееся
|
||||||
|
сообщение (зритель убрал его сам, прошло больше 48 часов) уборку не срывает — это не та задача,
|
||||||
|
ради которой стоит рвать разговор.
|
||||||
|
|
||||||
Данные кнопок несут действие: `sub:канал:вид` переключает подписку, `epg:канал` показывает
|
Данные кнопок несут действие: `sub:канал:вид` переключает подписку, `epg:канал` показывает
|
||||||
программу. Действие в первом поле, а не «угадаем по числу частей»: кнопок со временем станет
|
программу. Действие в первом поле, а не «угадаем по числу частей»: кнопок со временем станет
|
||||||
больше, и разбор по длине строки сломался бы на первой же новой.
|
больше, и разбор по длине строки сломался бы на первой же новой.
|
||||||
|
|||||||
Reference in New Issue
Block a user