using Telegram.Bot.Types;
using TelegramAuthBot.Models;
namespace TelegramAuthBot.Services
{
sealed partial class TelegramAuthBotSession
{
static readonly Regex UidRe = new(@"^[a-zA-Z0-9_-]{6,20}$", RegexOptions.Compiled);
static readonly Regex StartCommandRe = new(@"^/start(?:@\w+)?(?:\s+(\S+))?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
static readonly Regex AdminUserDetailRe = new(@"^/user(?:@\w+)?\s+(\d+)\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
static readonly Regex AdminSetUserRe = new(@"^/setuser(?:@\w+)?\s+(\d+)\s+([a-zA-Z_]+)\s*(.*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
const string BtnStatus = "👤 Мой статус";
const string BtnDevices = "📱 Мои устройства";
const string BtnHelp = "❓ Помощь";
const int AdminUsersPageSize = 8;
const string CbUsersPage = "ulp:";
const string CbDisableUser = "d|";
const string CbEnableUser = "e|";
const string CbApprovePending = "ap|";
const string CbRejectPending = "rp|";
const string CbReactivateDevice = "react:";
readonly LampacTelegramAuthHttpClient _api;
readonly string _displayName;
int _firstUpdateLogged;
public TelegramAuthBotSession(LampacTelegramAuthHttpClient api, string displayName)
{
_api = api;
_displayName = displayName;
}
public async Task HandleUpdateAsync(ITelegramBotClient bot, Update update, CancellationToken ct)
{
if (Interlocked.CompareExchange(ref _firstUpdateLogged, 1, 0) == 0)
TelegramAuthBotSerilog.Log.Information("Первый апдейт {UpdateId}", update.Id);
if (update.CallbackQuery is { } cq)
{
await HandleCallbackAsync(bot, cq, ct).ConfigureAwait(false);
return;
}
var msg = update.Message ?? update.EditedMessage;
if (msg is not { } m)
return;
var text = MessageTextOrCaption(m);
if (string.IsNullOrEmpty(text))
return;
if (!TryResolveTelegramUserId(m, out var tgId))
{
TelegramAuthBotSerilog.Log.Warning("Не удалось определить telegram user id UpdateId={UpdateId} ChatType={ChatType}", update.Id, m.Chat.Type);
await bot.SendMessage(m.Chat.Id,
"Не могу определить твой Telegram ID в этом чате. Напиши боту в личные сообщения.",
cancellationToken: ct).ConfigureAwait(false);
return;
}
await HandleMessageAsync(bot, m, text, tgId, ct).ConfigureAwait(false);
}
static string MessageTextOrCaption(Message msg)
{
if (!string.IsNullOrEmpty(msg.Text))
return msg.Text;
if (!string.IsNullOrEmpty(msg.Caption))
return msg.Caption;
return null;
}
static bool TryResolveTelegramUserId(Message msg, out string tgId)
{
if (msg.From != null)
{
tgId = msg.From.Id.ToString();
return true;
}
if (msg.Chat.Type == ChatType.Private)
{
tgId = msg.Chat.Id.ToString();
return true;
}
tgId = "";
return false;
}
static ReplyKeyboardMarkup MainMenuKeyboard() =>
new(new[]
{
new KeyboardButton[] { new(BtnStatus), new(BtnDevices) },
new KeyboardButton[] { new(BtnHelp) }
})
{
ResizeKeyboard = true
};
async Task SendStartText(ITelegramBotClient bot, ChatId chatId, CancellationToken ct)
{
var name = _displayName;
var text =
$"✨ Привет. Я бот авторизации {EscapeHtml(name)}.\n\n" +
$"С моей помощью можно быстро войти в {EscapeHtml(name)} через Telegram.\n\n" +
"Что я умею:\n" +
"• 🔗 привязать устройство по UID\n" +
"• 👤 показать твой статус\n" +
"• 📱 показать список устройств и переименовать (/devicename)\n" +
"• 🗑️ отвязать устройство кнопкой\n\n" +
"Как войти:\n" +
$"1. Открой {EscapeHtml(name)}\n" +
"2. Скопируй UID с экрана авторизации\n" +
"3. Отправь его мне\n" +
"4. Вернись в " + EscapeHtml(name) + " и нажми «Проверить снова»\n\n" +
"Или просто используй кнопки ниже 👇";
await bot.SendMessage(chatId, text, parseMode: ParseMode.Html, replyMarkup: MainMenuKeyboard(), cancellationToken: ct).ConfigureAwait(false);
}
async Task HandleMessageAsync(ITelegramBotClient bot, Message msg, string text, string tgId, CancellationToken ct)
{
var chatId = msg.Chat.Id;
var username = msg.From?.Username ?? "";
if (text.StartsWith("/start", StringComparison.OrdinalIgnoreCase))
{
var m = StartCommandRe.Match(text.Trim());
var deepUid = m.Success && m.Groups[1].Success ? m.Groups[1].Value.Trim() : "";
if (!string.IsNullOrEmpty(deepUid) && UidRe.IsMatch(deepUid))
{
var handled = await TryBindAsync(bot, chatId, tgId, username, deepUid, fromStartDeepLink: true, ct).ConfigureAwait(false);
if (handled)
return;
}
await SendStartText(bot, chatId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/help"))
{
await CmdHelpAsync(bot, chatId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/me"))
{
await CmdMeAsync(bot, chatId, tgId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/devices"))
{
await CmdDevicesAsync(bot, chatId, tgId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/devicename"))
{
await CmdDeviceNameAsync(bot, chatId, tgId, text, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/import"))
{
await CmdImportAsync(bot, msg.Chat, tgId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/cleanup"))
{
await CmdCleanupAsync(bot, msg.Chat, tgId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/users"))
{
await CmdUsersAsync(bot, msg.Chat, tgId, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/user"))
{
await CmdAdminUserDetailAsync(bot, msg.Chat, tgId, text, ct).ConfigureAwait(false);
return;
}
if (IsCommand(text, "/setuser"))
{
await CmdAdminSetUserAsync(bot, msg.Chat, tgId, text, ct).ConfigureAwait(false);
return;
}
var trimmed = text.Trim();
if (trimmed == BtnStatus)
{
await CmdMeAsync(bot, chatId, tgId, ct).ConfigureAwait(false);
return;
}
if (trimmed == BtnDevices)
{
await CmdDevicesAsync(bot, chatId, tgId, ct).ConfigureAwait(false);
return;
}
if (trimmed == BtnHelp)
{
await CmdHelpAsync(bot, chatId, ct).ConfigureAwait(false);
return;
}
if (trimmed.StartsWith('/'))
return;
if (UidRe.IsMatch(trimmed))
{
await TryBindAsync(bot, chatId, tgId, username, trimmed, fromStartDeepLink: false, ct).ConfigureAwait(false);
return;
}
var name = _displayName;
await bot.SendMessage(chatId,
$"Я жду UID устройства из {EscapeHtml(name)} или используй кнопки ниже 👇",
parseMode: ParseMode.Html,
replyMarkup: MainMenuKeyboard(),
cancellationToken: ct).ConfigureAwait(false);
}
static bool IsCommand(string text, string command)
{
var t = text.Trim();
if (!t.StartsWith(command, StringComparison.OrdinalIgnoreCase))
return false;
if (t.Length == command.Length)
return true;
var c = t[command.Length];
return c == ' ' || c == '@';
}
}
}