Refactor user handling in main.go to implement pagination for user lists. Introduce new functions for building user list messages and handling callback queries for page navigation. Enhance message formatting with HTML support for improved user experience.
Publish telemt-bot Docker image / build-and-push (push) Successful in 50s
Publish telemt-bot Docker image / build-and-push (push) Successful in 50s
This commit is contained in:
@@ -26,6 +26,9 @@ const (
|
||||
defaultTelemtTimeoutSeconds = 5
|
||||
defaultTelegramTimeoutSeconds = 60
|
||||
defaultListenPollTimeout = 50
|
||||
|
||||
usersPerPage = 12
|
||||
usersPageCbPrefix = "u_pg:"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
@@ -448,23 +451,8 @@ func (b *bot) handleUsers(ctx context.Context, chatID int64) error {
|
||||
return b.sendMessage(ctx, chatID, "Пользователи не найдены.")
|
||||
}
|
||||
|
||||
const maxRows = 20
|
||||
var sb strings.Builder
|
||||
if len(users) > maxRows {
|
||||
sb.WriteString(fmt.Sprintf("Показаны первые %d из %d\n", maxRows, len(users)))
|
||||
}
|
||||
limit := len(users)
|
||||
if limit > maxRows {
|
||||
limit = maxRows
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
u := users[i]
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
"%d) %s | conn=%d | ips=%d | octets=%d\n",
|
||||
i+1, u.Username, u.CurrentConnections, u.ActiveUniqueIPs, u.TotalOctets,
|
||||
))
|
||||
}
|
||||
return b.sendMessage(ctx, chatID, strings.TrimRight(sb.String(), "\n"))
|
||||
text, kb := buildUsersListMessage(users, 0)
|
||||
return b.sendMessageWithKeyboardHTML(ctx, chatID, text, kb, true)
|
||||
}
|
||||
|
||||
func (b *bot) handleCreateUser(ctx context.Context, chatID int64, text string) error {
|
||||
@@ -670,6 +658,17 @@ func (b *bot) handleCallbackQuery(ctx context.Context, cq *tgCallbackQuery) erro
|
||||
delete(b.awaitingUsername, chatID)
|
||||
return b.editMessageText(ctx, chatID, msgID, "Выберите действие:", mainMenuKeyboard())
|
||||
default:
|
||||
if strings.HasPrefix(cq.Data, usersPageCbPrefix) {
|
||||
rest := strings.TrimPrefix(cq.Data, usersPageCbPrefix)
|
||||
page, err := strconv.Atoi(rest)
|
||||
if err != nil || page < 0 {
|
||||
return b.answerCallbackQuery(ctx, cq.ID, "Некорректная страница", true)
|
||||
}
|
||||
if err := b.answerCallbackQuery(ctx, cq.ID, "", false); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.handleUsersPageCB(ctx, chatID, msgID, page)
|
||||
}
|
||||
return b.answerCallbackQuery(ctx, cq.ID, "Неизвестное действие", false)
|
||||
}
|
||||
}
|
||||
@@ -696,6 +695,10 @@ func (b *bot) handleSummaryCB(ctx context.Context, chatID, msgID int64) error {
|
||||
}
|
||||
|
||||
func (b *bot) handleUsersCB(ctx context.Context, chatID, msgID int64) error {
|
||||
return b.handleUsersPageCB(ctx, chatID, msgID, 0)
|
||||
}
|
||||
|
||||
func (b *bot) handleUsersPageCB(ctx context.Context, chatID, msgID int64, page int) error {
|
||||
var users []userInfo
|
||||
if err := b.callTelemt(ctx, "/v1/stats/users", &users); err != nil {
|
||||
return b.editMessageText(ctx, chatID, msgID, "Ошибка API: "+err.Error(), backKeyboard())
|
||||
@@ -705,23 +708,123 @@ func (b *bot) handleUsersCB(ctx context.Context, chatID, msgID int64) error {
|
||||
return b.editMessageText(ctx, chatID, msgID, "Пользователи не найдены.", backKeyboard())
|
||||
}
|
||||
|
||||
const maxRows = 20
|
||||
totalPages := usersTotalPages(len(users))
|
||||
if page >= totalPages {
|
||||
page = totalPages - 1
|
||||
}
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
|
||||
text, kb := buildUsersListMessage(users, page)
|
||||
return b.editMessageTextHTML(ctx, chatID, msgID, text, kb, true)
|
||||
}
|
||||
|
||||
func usersTotalPages(n int) int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
pages := n / usersPerPage
|
||||
if n%usersPerPage != 0 {
|
||||
pages++
|
||||
}
|
||||
if pages < 1 {
|
||||
return 1
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
func escapeHTML(s string) string {
|
||||
s = strings.ReplaceAll(s, "&", "&")
|
||||
s = strings.ReplaceAll(s, "<", "<")
|
||||
s = strings.ReplaceAll(s, ">", ">")
|
||||
return s
|
||||
}
|
||||
|
||||
// formatTrafficRu форматирует объём в октетах (байтах) в удобочитаемый вид (двоичные приставки).
|
||||
func formatTrafficRu(octets uint64) string {
|
||||
if octets == 0 {
|
||||
return "0 Б"
|
||||
}
|
||||
const (
|
||||
kib = 1024
|
||||
mib = kib * 1024
|
||||
gib = mib * 1024
|
||||
tib = gib * 1024
|
||||
)
|
||||
switch {
|
||||
case octets >= tib:
|
||||
return fmt.Sprintf("%.2f ТиБ", float64(octets)/float64(tib))
|
||||
case octets >= gib:
|
||||
return fmt.Sprintf("%.2f ГиБ", float64(octets)/float64(gib))
|
||||
case octets >= mib:
|
||||
return fmt.Sprintf("%.2f МиБ", float64(octets)/float64(mib))
|
||||
case octets >= kib:
|
||||
return fmt.Sprintf("%.1f КиБ", float64(octets)/float64(kib))
|
||||
default:
|
||||
return fmt.Sprintf("%d Б", octets)
|
||||
}
|
||||
}
|
||||
|
||||
func buildUsersListMessage(users []userInfo, page int) (string, *inlineKeyboardMarkup) {
|
||||
total := len(users)
|
||||
totalPages := usersTotalPages(total)
|
||||
start := page * usersPerPage
|
||||
if start > total {
|
||||
start = 0
|
||||
}
|
||||
end := start + usersPerPage
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if len(users) > maxRows {
|
||||
sb.WriteString(fmt.Sprintf("Показаны первые %d из %d\n", maxRows, len(users)))
|
||||
}
|
||||
limit := len(users)
|
||||
if limit > maxRows {
|
||||
limit = maxRows
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
sb.WriteString("<b>Пользователи</b>")
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(fmt.Sprintf("<i>Страница %d из %d · всего записей: %d</i>", page+1, totalPages, total))
|
||||
sb.WriteString("\n\n")
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
u := users[i]
|
||||
globalIdx := i + 1
|
||||
sb.WriteString(fmt.Sprintf("<b>%d.</b> <code>%s</code>\n", globalIdx, escapeHTML(u.Username)))
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
"%d) %s | conn=%d | ips=%d | octets=%d\n",
|
||||
i+1, u.Username, u.CurrentConnections, u.ActiveUniqueIPs, u.TotalOctets,
|
||||
" соединений: <b>%d</b> · уник. IP: <b>%d</b> · трафик: <b>%s</b>\n",
|
||||
u.CurrentConnections, u.ActiveUniqueIPs, formatTrafficRu(u.TotalOctets),
|
||||
))
|
||||
if i+1 < end {
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return b.editMessageText(ctx, chatID, msgID, strings.TrimRight(sb.String(), "\n"), backKeyboard())
|
||||
|
||||
kb := usersListKeyboard(page, totalPages)
|
||||
return strings.TrimRight(sb.String(), "\n"), kb
|
||||
}
|
||||
|
||||
func usersListKeyboard(page, totalPages int) *inlineKeyboardMarkup {
|
||||
var rows [][]inlineKeyboardButton
|
||||
var nav []inlineKeyboardButton
|
||||
if page > 0 {
|
||||
nav = append(nav, inlineKeyboardButton{
|
||||
Text: "◀ Назад",
|
||||
CallbackData: usersPageCbPrefix + strconv.Itoa(page-1),
|
||||
Style: "primary",
|
||||
})
|
||||
}
|
||||
if page < totalPages-1 {
|
||||
nav = append(nav, inlineKeyboardButton{
|
||||
Text: "Вперёд ▶",
|
||||
CallbackData: usersPageCbPrefix + strconv.Itoa(page+1),
|
||||
Style: "primary",
|
||||
})
|
||||
}
|
||||
if len(nav) > 0 {
|
||||
rows = append(rows, nav)
|
||||
}
|
||||
rows = append(rows, []inlineKeyboardButton{
|
||||
{Text: "« В меню", CallbackData: "cmd_menu"},
|
||||
})
|
||||
return &inlineKeyboardMarkup{InlineKeyboard: rows}
|
||||
}
|
||||
|
||||
func (b *bot) handleCreateUserDirect(ctx context.Context, chatID int64, username string) error {
|
||||
@@ -849,6 +952,15 @@ func (b *bot) sendMessage(ctx context.Context, chatID int64, text string) error
|
||||
}
|
||||
|
||||
func (b *bot) sendMessageWithKeyboard(ctx context.Context, chatID int64, text string, keyboard *inlineKeyboardMarkup) error {
|
||||
return b.sendMessageWithKeyboardOpts(ctx, chatID, text, keyboard, "", false)
|
||||
}
|
||||
|
||||
// sendMessageWithKeyboardHTML — сообщение с разметкой HTML (см. Bot API: formatting options, link_preview_options).
|
||||
func (b *bot) sendMessageWithKeyboardHTML(ctx context.Context, chatID int64, text string, keyboard *inlineKeyboardMarkup, disableLinkPreview bool) error {
|
||||
return b.sendMessageWithKeyboardOpts(ctx, chatID, text, keyboard, "HTML", disableLinkPreview)
|
||||
}
|
||||
|
||||
func (b *bot) sendMessageWithKeyboardOpts(ctx context.Context, chatID int64, text string, keyboard *inlineKeyboardMarkup, parseMode string, disableLinkPreview bool) error {
|
||||
reqBody := map[string]any{
|
||||
"chat_id": chatID,
|
||||
"text": text,
|
||||
@@ -856,10 +968,24 @@ func (b *bot) sendMessageWithKeyboard(ctx context.Context, chatID int64, text st
|
||||
if keyboard != nil {
|
||||
reqBody["reply_markup"] = keyboard
|
||||
}
|
||||
if parseMode != "" {
|
||||
reqBody["parse_mode"] = parseMode
|
||||
}
|
||||
if disableLinkPreview {
|
||||
reqBody["link_preview_options"] = map[string]bool{"is_disabled": true}
|
||||
}
|
||||
return b.postTelegram(ctx, "/sendMessage", reqBody)
|
||||
}
|
||||
|
||||
func (b *bot) editMessageText(ctx context.Context, chatID, messageID int64, text string, keyboard *inlineKeyboardMarkup) error {
|
||||
return b.editMessageTextOpts(ctx, chatID, messageID, text, keyboard, "", false)
|
||||
}
|
||||
|
||||
func (b *bot) editMessageTextHTML(ctx context.Context, chatID, messageID int64, text string, keyboard *inlineKeyboardMarkup, disableLinkPreview bool) error {
|
||||
return b.editMessageTextOpts(ctx, chatID, messageID, text, keyboard, "HTML", disableLinkPreview)
|
||||
}
|
||||
|
||||
func (b *bot) editMessageTextOpts(ctx context.Context, chatID, messageID int64, text string, keyboard *inlineKeyboardMarkup, parseMode string, disableLinkPreview bool) error {
|
||||
reqBody := map[string]any{
|
||||
"chat_id": chatID,
|
||||
"message_id": messageID,
|
||||
@@ -868,6 +994,12 @@ func (b *bot) editMessageText(ctx context.Context, chatID, messageID int64, text
|
||||
if keyboard != nil {
|
||||
reqBody["reply_markup"] = keyboard
|
||||
}
|
||||
if parseMode != "" {
|
||||
reqBody["parse_mode"] = parseMode
|
||||
}
|
||||
if disableLinkPreview {
|
||||
reqBody["link_preview_options"] = map[string]bool{"is_disabled": true}
|
||||
}
|
||||
return b.postTelegram(ctx, "/editMessageText", reqBody)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user