Enhance /admin with full panel and subscription health checks
This commit is contained in:
+89
-37
@@ -16,10 +16,10 @@ import (
|
||||
const docsURL = "https://docs.rw/"
|
||||
|
||||
type Handler struct {
|
||||
cfg *config.Config
|
||||
api *tgbotapi.BotAPI
|
||||
panel *remnawave.Client
|
||||
admin int64
|
||||
cfg *config.Config
|
||||
api *tgbotapi.BotAPI
|
||||
panel *remnawave.Client
|
||||
admin int64
|
||||
}
|
||||
|
||||
func NewHandler(cfg *config.Config, api *tgbotapi.BotAPI) *Handler {
|
||||
@@ -31,6 +31,21 @@ func NewHandler(cfg *config.Config, api *tgbotapi.BotAPI) *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterCommands() {
|
||||
commands := []tgbotapi.BotCommand{
|
||||
{Command: "start", Description: "Начать"},
|
||||
{Command: "admin", Description: "Админ-меню Remnawave (панель 1)"},
|
||||
}
|
||||
scope := tgbotapi.BotCommandScope{Type: "chat", ChatID: h.admin}
|
||||
cfg := tgbotapi.SetMyCommandsConfig{
|
||||
Commands: commands,
|
||||
Scope: &scope,
|
||||
}
|
||||
if _, err := h.api.Request(cfg); err != nil {
|
||||
log.Printf("не удалось зарегистрировать команды для админа: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) HandleUpdate(update tgbotapi.Update) {
|
||||
if update.CallbackQuery != nil {
|
||||
h.handleCallback(update.CallbackQuery)
|
||||
@@ -47,12 +62,8 @@ func (h *Handler) HandleUpdate(update tgbotapi.Update) {
|
||||
switch {
|
||||
case text == "/start":
|
||||
h.sendStart(chatID, userID, update.Message.From.FirstName)
|
||||
case text == "/admin":
|
||||
if !h.isAdmin(userID) {
|
||||
h.sendText(chatID, "У вас нет доступа к админ-меню.")
|
||||
return
|
||||
}
|
||||
h.sendAdminMenu(chatID, "Админ-меню VPN-панели Remnawave:")
|
||||
case strings.HasPrefix(text, "/admin"):
|
||||
h.handleAdminCommand(chatID, userID, text)
|
||||
case strings.HasPrefix(text, "/"):
|
||||
h.sendText(chatID, "Неизвестная команда. Для начала — /start")
|
||||
default:
|
||||
@@ -73,6 +84,28 @@ func (h *Handler) HandleUpdate(update tgbotapi.Update) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleAdminCommand(chatID, userID int64, text string) {
|
||||
if !h.isAdmin(userID) {
|
||||
h.sendText(chatID, "У вас нет доступа к админ-меню.")
|
||||
return
|
||||
}
|
||||
|
||||
args := strings.Fields(text)
|
||||
if len(args) == 1 {
|
||||
h.sendAdminMenu(chatID)
|
||||
return
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
case "check", "проверка":
|
||||
h.sendPanelCheck(chatID)
|
||||
case "config", "конфиг":
|
||||
h.sendPanelConfig(chatID)
|
||||
default:
|
||||
h.sendAdminHelp(chatID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleCallback(cq *tgbotapi.CallbackQuery) {
|
||||
answer := tgbotapi.NewCallback(cq.ID, "")
|
||||
if _, err := h.api.Request(answer); err != nil {
|
||||
@@ -90,7 +123,7 @@ func (h *Handler) handleCallback(cq *tgbotapi.CallbackQuery) {
|
||||
case "admin:check":
|
||||
h.sendPanelCheck(cq.Message.Chat.ID)
|
||||
case "admin:menu":
|
||||
h.sendAdminMenu(cq.Message.Chat.ID, "Админ-меню VPN-панели Remnawave:")
|
||||
h.sendAdminMenu(cq.Message.Chat.ID)
|
||||
default:
|
||||
h.editOrSend(cq.Message.Chat.ID, cq.Message.MessageID, "Неизвестное действие.")
|
||||
}
|
||||
@@ -107,7 +140,7 @@ func (h *Handler) sendStart(chatID, userID int64, firstName string) {
|
||||
}
|
||||
text := fmt.Sprintf("Привет, %s!\n\nЯ VPN-бот на базе панели Remnawave.", name)
|
||||
if h.isAdmin(userID) {
|
||||
text += "\n\nКоманда /admin — настройки и проверка панели."
|
||||
text += "\n\n/admin — админ-меню\n/admin check — проверка API и подписки"
|
||||
}
|
||||
msg := tgbotapi.NewMessage(chatID, text)
|
||||
if h.isAdmin(userID) {
|
||||
@@ -116,22 +149,42 @@ func (h *Handler) sendStart(chatID, userID int64, firstName string) {
|
||||
h.send(msg)
|
||||
}
|
||||
|
||||
func (h *Handler) sendAdminMenu(chatID int64, title string) {
|
||||
msg := tgbotapi.NewMessage(chatID, title)
|
||||
func (h *Handler) sendAdminMenu(chatID int64) {
|
||||
text := fmt.Sprintf(
|
||||
"🛠 *Админ-меню* — %s\n\n"+
|
||||
"Команды:\n"+
|
||||
"• /admin — это меню\n"+
|
||||
"• /admin check — проверка панели, API и подписки\n"+
|
||||
"• /admin config — конфиг панели\n\n"+
|
||||
"Или кнопки ниже.",
|
||||
escapeMarkdown(h.cfg.RemnawaveName),
|
||||
)
|
||||
msg := tgbotapi.NewMessage(chatID, text)
|
||||
msg.ParseMode = "Markdown"
|
||||
msg.ReplyMarkup = adminInlineKeyboard()
|
||||
h.send(msg)
|
||||
}
|
||||
|
||||
func (h *Handler) sendAdminHelp(chatID int64) {
|
||||
h.sendText(chatID, "Неизвестный аргумент.\n\n/admin — меню\n/admin check — проверка\n/admin config — конфиг")
|
||||
}
|
||||
|
||||
func (h *Handler) sendPanelConfig(chatID int64) {
|
||||
subURL := h.cfg.RemnawaveSubscription
|
||||
if subURL == "" {
|
||||
subURL = "не задан"
|
||||
}
|
||||
text := fmt.Sprintf(
|
||||
"⚙️ *%s* (Remnawave)\n\n"+
|
||||
"• URL: `%s`\n"+
|
||||
"• URL панели: `%s`\n"+
|
||||
"• URL подписки: `%s`\n"+
|
||||
"• API token: `%s`\n"+
|
||||
"• Caddy token: %s\n\n"+
|
||||
"Токен API создаётся в панели: *Settings → API Tokens*.\n"+
|
||||
"Токен API: панель → *Settings → API Tokens*.\n"+
|
||||
"Документация: %s",
|
||||
escapeMarkdown(h.cfg.RemnawaveName),
|
||||
escapeMarkdown(h.cfg.RemnawaveURL),
|
||||
escapeMarkdown(subURL),
|
||||
escapeMarkdown(maskSecret(h.cfg.RemnawaveToken)),
|
||||
caddyStatus(h.cfg.RemnawaveCaddy),
|
||||
docsURL,
|
||||
@@ -143,41 +196,35 @@ func (h *Handler) sendPanelConfig(chatID int64) {
|
||||
}
|
||||
|
||||
func (h *Handler) sendPanelCheck(chatID int64) {
|
||||
h.sendText(chatID, "Проверяю подключение к панели…")
|
||||
h.sendText(chatID, fmt.Sprintf("Проверяю «%s»: панель, API, подписка…", h.cfg.RemnawaveName))
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
|
||||
st, err := h.panel.Check(ctx)
|
||||
if err != nil {
|
||||
h.sendText(chatID, fmt.Sprintf("❌ %s\n\nПанель: %s", err.Error(), h.cfg.RemnawaveURL))
|
||||
return
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"✅ %s\n\nПанель: *%s*\nURL: `%s`\nHTTP: %d",
|
||||
st.Detail,
|
||||
report := h.panel.FullCheck(ctx, h.cfg.RemnawaveSubscription)
|
||||
text := remnawave.FormatReport(
|
||||
report,
|
||||
escapeMarkdown(h.cfg.RemnawaveName),
|
||||
escapeMarkdown(h.cfg.RemnawaveURL),
|
||||
st.StatusCode,
|
||||
)
|
||||
if st.Users > 0 || st.Nodes > 0 {
|
||||
text += fmt.Sprintf("\n\n👥 Пользователей: %d\n📡 Нод: %d", st.Users, st.Nodes)
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewMessage(chatID, text)
|
||||
msg.ParseMode = "Markdown"
|
||||
msg.ReplyMarkup = adminInlineKeyboard()
|
||||
h.send(msg)
|
||||
if err := h.sendReturnErr(msg); err != nil {
|
||||
msg.ParseMode = ""
|
||||
h.send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func adminInlineKeyboard() tgbotapi.InlineKeyboardMarkup {
|
||||
return tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("📋 Конфиг панели", "admin:config"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("🔌 Проверить панель", "admin:check"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("🔌 Проверить (API+подписка)", "admin:check"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("📋 Конфиг", "admin:config"),
|
||||
),
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonURL("📖 Документация Remnawave", docsURL),
|
||||
tgbotapi.NewInlineKeyboardButtonURL("📖 Документация", docsURL),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -186,8 +233,8 @@ func adminReplyKeyboard() tgbotapi.ReplyKeyboardMarkup {
|
||||
return tgbotapi.ReplyKeyboardMarkup{
|
||||
Keyboard: [][]tgbotapi.KeyboardButton{
|
||||
{
|
||||
tgbotapi.NewKeyboardButton("📋 Конфиг панели"),
|
||||
tgbotapi.NewKeyboardButton("🔌 Проверить панель"),
|
||||
tgbotapi.NewKeyboardButton("📋 Конфиг панели"),
|
||||
},
|
||||
{
|
||||
tgbotapi.NewKeyboardButton("◀️ Выйти из админки"),
|
||||
@@ -203,11 +250,16 @@ func (h *Handler) sendText(chatID int64, text string) {
|
||||
}
|
||||
|
||||
func (h *Handler) send(msg tgbotapi.MessageConfig) {
|
||||
if _, err := h.api.Send(msg); err != nil {
|
||||
if err := h.sendReturnErr(msg); err != nil {
|
||||
log.Printf("ошибка отправки: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) sendReturnErr(msg tgbotapi.MessageConfig) error {
|
||||
_, err := h.api.Send(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) editOrSend(chatID int64, messageID int, text string) {
|
||||
edit := tgbotapi.NewEditMessageText(chatID, messageID, text)
|
||||
if _, err := h.api.Send(edit); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user