package bot import ( "context" "fmt" "strings" "github.com/go-telegram/bot/models" ) // LeagueSortOption represents a valid league sort column. type LeagueSortOption string const ( LeagueSortXP LeagueSortOption = "xp" LeagueSortLevel LeagueSortOption = "level" LeagueSortStreak LeagueSortOption = "streak" LeagueSortHours LeagueSortOption = "hours" ) // buildLeagueText returns the formatted league rankings (mobile-friendly narrow layout). func (h *Handler) buildLeagueText(orderBy LeagueSortOption) string { entries, err := h.DB.GetLeagueRankings(string(orderBy)) if err != nil { return "League rankings unavailable." } if len(entries) == 0 { return "No participants in the league yet.\nEnable League participation in Settings to join!" } var b strings.Builder orderLabel := map[string]string{ "xp": "XP", "level": "Level", "streak": "Streak", "hours": "Hours", } b.WriteString(fmt.Sprintf("WorkTime League — %s\n\n", orderLabel[string(orderBy)])) for i, e := range entries { rank := i + 1 name := e.Username if name == "" { name = "Anonymous" } b.WriteString(fmt.Sprintf("%d %s\n", rank, name)) b.WriteString(fmt.Sprintf(" XP:%d Lv:%d S:%d %.1fh\n", e.XP, e.Level, e.Streak, e.TotalHours)) } b.WriteString(fmt.Sprintf("\n%d participant(s)\n", len(entries))) return b.String() } // buildLeagueRankText returns the user's personal rank in the league. func (h *Handler) buildLeagueRankText(userID int64) string { entries, err := h.DB.GetLeagueRankings("xp") if err != nil || len(entries) == 0 { return "" } for i, e := range entries { if e.UserID == userID { return fmt.Sprintf("League rank: #%d of %d", i+1, len(entries)) } } return "" } // handleLeagueMsg handles the /league command. func (h *Handler) handleLeagueMsg(ctx context.Context, msg *models.Message) { user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } st, err := h.DB.GetOrCreateSettings(user.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading settings") return } if !st.LeagueOptIn { h.sendText(ctx, msg.Chat.ID, "League participation is disabled.\nEnable it in Settings or use /leaguetoggle.") return } text := h.buildLeagueText(LeagueSortXP) kb := h.buildLeagueKeyboard("xp") h.sendWithKB(ctx, msg.Chat.ID, text, kb) } // handleLeagueCallback handles league inline view. func (h *Handler) handleLeagueCallback(ctx context.Context, chatID int64, msgID int, callbackID, orderBy string) { defer h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } st, err := h.DB.GetOrCreateSettings(user.ID) if err != nil { return } if !st.LeagueOptIn { kb := backKeyboard() h.editText(ctx, chatID, msgID, "League participation is disabled.\nEnable it in Settings to view rankings.", &kb) return } text := h.buildLeagueText(LeagueSortOption(orderBy)) kb := h.buildLeagueKeyboard(orderBy) h.editText(ctx, chatID, msgID, text, &kb) } // buildLeagueKeyboard creates the league navigation keyboard (2x2 sort grid). func (h *Handler) buildLeagueKeyboard(currentOrder string) models.InlineKeyboardMarkup { type sortBtn struct { label string data string } orders := []sortBtn{ {"XP", "league_xp"}, {"Level", "league_level"}, {"Streak", "league_streak"}, {"Hours", "league_hours"}, } rows := [][]models.InlineKeyboardButton{} for i := 0; i < len(orders); i += 2 { row := []models.InlineKeyboardButton{} for j := i; j < i+2 && j < len(orders); j++ { label := orders[j].label if orders[j].data == "league_"+currentOrder { label = "> " + label } row = append(row, models.InlineKeyboardButton{Text: label, CallbackData: orders[j].data}) } rows = append(rows, row) } rows = append(rows, []models.InlineKeyboardButton{ {Text: "Back to Menu", CallbackData: "back_menu"}, }) return models.InlineKeyboardMarkup{InlineKeyboard: rows} }