fix: currency validation, league mobile layout, settings 2-col, burnout ratio, add setusername
- Require both symbol+code in /setcurrency; add preset picker ($ USD, T Toman, IRR Rial, EUR, GBP) in settings inline menu - League: compact 2-line per entry for mobile; sort buttons in 2x2 grid - Settings: 2-column layout with grouped buttons (timezone+calendar, report+report time, break+accent, rpg+league, currency+rate) - Burnout: cap break ratio at 100% to prevent nonsense values - Add /setusername command (uses existing SanitizeDisplayName) - Update /help with new/changed commands
This commit is contained in:
@@ -258,6 +258,9 @@ func (h *Handler) buildBurnoutText(userID int64, loc *time.Location) string {
|
|||||||
br := float64(res.BreakSeconds)
|
br := float64(res.BreakSeconds)
|
||||||
total := float64(res.WorkSeconds + res.BreakSeconds)
|
total := float64(res.WorkSeconds + res.BreakSeconds)
|
||||||
ratio := br / total * 100
|
ratio := br / total * 100
|
||||||
|
if ratio > 100 {
|
||||||
|
ratio = 100
|
||||||
|
}
|
||||||
b.WriteString(fmt.Sprintf(" Break ratio: %.0f%% (%d pts)\n", ratio, res.BreakRatioPts))
|
b.WriteString(fmt.Sprintf(" Break ratio: %.0f%% (%d pts)\n", ratio, res.BreakRatioPts))
|
||||||
}
|
}
|
||||||
if res.WeekendPts > 0 {
|
if res.WeekendPts > 0 {
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ func (h *Handler) HandleMessage(ctx context.Context, msg *models.Message) {
|
|||||||
h.handleRPGSettingsMsg(ctx, msg)
|
h.handleRPGSettingsMsg(ctx, msg)
|
||||||
case "/leaguetoggle":
|
case "/leaguetoggle":
|
||||||
h.handleLeagueSettingsMsg(ctx, msg)
|
h.handleLeagueSettingsMsg(ctx, msg)
|
||||||
|
case "/setusername":
|
||||||
|
h.handleSetUsername(ctx, msg)
|
||||||
default:
|
default:
|
||||||
h.sendText(ctx, msg.Chat.ID, "Unknown command")
|
h.sendText(ctx, msg.Chat.ID, "Unknown command")
|
||||||
}
|
}
|
||||||
@@ -326,6 +328,10 @@ func (h *Handler) handlePrefixedCallback(ctx context.Context, chatID int64, msgI
|
|||||||
h.selectBreakThreshold(ctx, chatID, msgID, callbackID, data[15:])
|
h.selectBreakThreshold(ctx, chatID, msgID, callbackID, data[15:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(data) > 9 && data[:9] == "currency_" {
|
||||||
|
h.selectCurrency(ctx, chatID, msgID, callbackID, data[9:])
|
||||||
|
return
|
||||||
|
}
|
||||||
switch data {
|
switch data {
|
||||||
case "rpg_enable":
|
case "rpg_enable":
|
||||||
h.handleSelectCallback(ctx, chatID, msgID, callbackID, "rpg_enable", true)
|
h.handleSelectCallback(ctx, chatID, msgID, callbackID, "rpg_enable", true)
|
||||||
@@ -399,13 +405,14 @@ func (h *Handler) handleHelp(ctx context.Context, msg *models.Message) {
|
|||||||
/setaccent - Select accent color
|
/setaccent - Select accent color
|
||||||
/settimezone <name> - Set timezone (e.g. Asia/Tehran)
|
/settimezone <name> - Set timezone (e.g. Asia/Tehran)
|
||||||
/setbreak <minutes> - Set break threshold in minutes
|
/setbreak <minutes> - Set break threshold in minutes
|
||||||
|
/setusername <name> - Set your display name
|
||||||
/rpg - Show RPG stats and XP
|
/rpg - Show RPG stats and XP
|
||||||
/achievements - View unlocked achievements
|
/achievements - View unlocked achievements
|
||||||
/salary - Show salary estimate
|
/salary - Show salary estimate
|
||||||
/burnout - View burnout assessment
|
/burnout - View burnout assessment
|
||||||
/league - View WorkTime League rankings
|
/league - View WorkTime League rankings
|
||||||
/setrate <amount> - Set hourly rate (e.g. 25.50)
|
/setrate <amount> - Set hourly rate (e.g. 25.50)
|
||||||
/setcurrency <symbol> [code] - Set currency (e.g. $ USD)
|
/setcurrency <symbol> <code> - Set currency (e.g. $ USD)
|
||||||
/rpgtoggle - Enable/disable RPG system
|
/rpgtoggle - Enable/disable RPG system
|
||||||
/leaguetoggle - Enable/disable league participation
|
/leaguetoggle - Enable/disable league participation
|
||||||
|
|
||||||
@@ -414,6 +421,36 @@ Buttons on the main menu also work.`
|
|||||||
h.sendText(ctx, msg.Chat.ID, text)
|
h.sendText(ctx, msg.Chat.ID, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleSetUsername handles /setusername <name>.
|
||||||
|
func (h *Handler) handleSetUsername(ctx context.Context, msg *models.Message) {
|
||||||
|
name := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setusername"))
|
||||||
|
if name == "" {
|
||||||
|
user, err := h.getOrCreateUser(msg.Chat.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.sendText(ctx, msg.Chat.ID, "Error loading profile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current username: %s\nUsage: /setusername <name>", user.Username))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = SanitizeDisplayName(name)
|
||||||
|
if name == "" {
|
||||||
|
h.sendText(ctx, msg.Chat.ID, "Invalid username.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err := h.getOrCreateUser(msg.Chat.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.sendText(ctx, msg.Chat.ID, "Error loading profile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Username = name
|
||||||
|
if err := h.DB.UpdateUser(user); err != nil {
|
||||||
|
h.sendText(ctx, msg.Chat.ID, "Error saving username")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Username set to: %s", name))
|
||||||
|
}
|
||||||
|
|
||||||
// handleNoteMsg processes the /note command: /note [YYYY-MM-DD] HH:MM <text>
|
// handleNoteMsg processes the /note command: /note [YYYY-MM-DD] HH:MM <text>
|
||||||
func (h *Handler) handleNoteMsg(ctx context.Context, msg *models.Message) {
|
func (h *Handler) handleNoteMsg(ctx context.Context, msg *models.Message) {
|
||||||
user, err := h.getOrCreateUser(msg.Chat.ID)
|
user, err := h.getOrCreateUser(msg.Chat.ID)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const (
|
|||||||
LeagueSortHours LeagueSortOption = "hours"
|
LeagueSortHours LeagueSortOption = "hours"
|
||||||
)
|
)
|
||||||
|
|
||||||
// buildLeagueText returns the formatted league rankings.
|
// buildLeagueText returns the formatted league rankings (mobile-friendly narrow layout).
|
||||||
func (h *Handler) buildLeagueText(orderBy LeagueSortOption) string {
|
func (h *Handler) buildLeagueText(orderBy LeagueSortOption) string {
|
||||||
entries, err := h.DB.GetLeagueRankings(string(orderBy))
|
entries, err := h.DB.GetLeagueRankings(string(orderBy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -33,37 +33,15 @@ func (h *Handler) buildLeagueText(orderBy LeagueSortOption) string {
|
|||||||
"xp": "XP",
|
"xp": "XP",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"streak": "Streak",
|
"streak": "Streak",
|
||||||
"hours": "Total Hours",
|
"hours": "Hours",
|
||||||
}
|
}
|
||||||
b.WriteString(fmt.Sprintf("WorkTime League — sorted by %s\n\n", orderLabel[string(orderBy)]))
|
b.WriteString(fmt.Sprintf("WorkTime League — %s\n\n", orderLabel[string(orderBy)]))
|
||||||
|
|
||||||
// Determine column widths
|
|
||||||
maxUserLen := 0
|
|
||||||
for _, e := range entries {
|
|
||||||
l := len(e.Username)
|
|
||||||
if l > maxUserLen {
|
|
||||||
maxUserLen = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maxUserLen < 8 {
|
|
||||||
maxUserLen = 8
|
|
||||||
}
|
|
||||||
if maxUserLen > 20 {
|
|
||||||
maxUserLen = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
header := fmt.Sprintf("%-3s %-*s %8s %6s %6s %12s", "#", maxUserLen, "Username", "XP", "Level", "Streak", "Hours")
|
|
||||||
b.WriteString(header + "\n")
|
|
||||||
b.WriteString(strings.Repeat("-", len(header)) + "\n")
|
|
||||||
|
|
||||||
for i, e := range entries {
|
for i, e := range entries {
|
||||||
rank := i + 1
|
rank := i + 1
|
||||||
name := e.Username
|
name := e.Username
|
||||||
if len(name) > maxUserLen {
|
b.WriteString(fmt.Sprintf("%d %s\n", rank, name))
|
||||||
name = name[:maxUserLen]
|
b.WriteString(fmt.Sprintf(" XP:%d Lv:%d S:%d %.1fh\n", e.XP, e.Level, e.Streak, e.TotalHours))
|
||||||
}
|
|
||||||
b.WriteString(fmt.Sprintf("%-3d %-*s %8d %6d %6d %12.1f\n",
|
|
||||||
rank, maxUserLen, name, e.XP, e.Level, e.Streak, e.TotalHours))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString(fmt.Sprintf("\n%d participant(s)\n", len(entries)))
|
b.WriteString(fmt.Sprintf("\n%d participant(s)\n", len(entries)))
|
||||||
@@ -128,12 +106,13 @@ func (h *Handler) handleLeagueCallback(ctx context.Context, chatID int64, msgID
|
|||||||
h.editText(ctx, chatID, msgID, text, &kb)
|
h.editText(ctx, chatID, msgID, text, &kb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildLeagueKeyboard creates the league navigation keyboard.
|
// buildLeagueKeyboard creates the league navigation keyboard (2x2 sort grid).
|
||||||
func (h *Handler) buildLeagueKeyboard(currentOrder string) models.InlineKeyboardMarkup {
|
func (h *Handler) buildLeagueKeyboard(currentOrder string) models.InlineKeyboardMarkup {
|
||||||
orders := []struct {
|
type sortBtn struct {
|
||||||
label string
|
label string
|
||||||
data string
|
data string
|
||||||
}{
|
}
|
||||||
|
orders := []sortBtn{
|
||||||
{"XP", "league_xp"},
|
{"XP", "league_xp"},
|
||||||
{"Level", "league_level"},
|
{"Level", "league_level"},
|
||||||
{"Streak", "league_streak"},
|
{"Streak", "league_streak"},
|
||||||
@@ -141,14 +120,16 @@ func (h *Handler) buildLeagueKeyboard(currentOrder string) models.InlineKeyboard
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows := [][]models.InlineKeyboardButton{}
|
rows := [][]models.InlineKeyboardButton{}
|
||||||
for _, o := range orders {
|
for i := 0; i < len(orders); i += 2 {
|
||||||
label := o.label
|
row := []models.InlineKeyboardButton{}
|
||||||
if o.data == "league_"+currentOrder {
|
for j := i; j < i+2 && j < len(orders); j++ {
|
||||||
|
label := orders[j].label
|
||||||
|
if orders[j].data == "league_"+currentOrder {
|
||||||
label = "> " + label
|
label = "> " + label
|
||||||
}
|
}
|
||||||
rows = append(rows, []models.InlineKeyboardButton{
|
row = append(row, models.InlineKeyboardButton{Text: label, CallbackData: orders[j].data})
|
||||||
{Text: label, CallbackData: o.data},
|
}
|
||||||
})
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
rows = append(rows, []models.InlineKeyboardButton{
|
rows = append(rows, []models.InlineKeyboardButton{
|
||||||
{Text: "Back to Menu", CallbackData: "back_menu"},
|
{Text: "Back to Menu", CallbackData: "back_menu"},
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func (h *Handler) handleSetRateMsg(ctx context.Context, msg *models.Message) {
|
|||||||
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Hourly rate set to %s%.2f", currencySymbol(st.Currency), st.HourlyRate))
|
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Hourly rate set to %s%.2f", currencySymbol(st.Currency), st.HourlyRate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSetCurrencyMsg handles /setcurrency <symbol> [code].
|
// handleSetCurrencyMsg handles /setcurrency <symbol> <code>.
|
||||||
func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message) {
|
func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message) {
|
||||||
args := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setcurrency"))
|
args := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setcurrency"))
|
||||||
if args == "" {
|
if args == "" {
|
||||||
@@ -189,18 +189,23 @@ func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
||||||
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current currency: %s\nUsage: /setcurrency <symbol> [code]\nExamples:\n/setcurrency $ USD\n/setcurrency EUR\n/setcurrency Toman", st.Currency))
|
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current currency: %s\nUsage: /setcurrency <symbol> <code>\nExamples:\n/setcurrency $ USD\n/setcurrency T Toman\n/setcurrency IRR Rial", st.Currency))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
parts := strings.Fields(args)
|
parts := strings.Fields(args)
|
||||||
if len(parts[0]) > 10 || strings.ContainsFunc(parts[0], func(r rune) bool { return r < 32 || r > 126 }) {
|
if len(parts) < 2 {
|
||||||
h.sendText(ctx, msg.Chat.ID, "Invalid currency symbol. Use a short printable string like $, €, £, or Toman.")
|
h.sendText(ctx, msg.Chat.ID, "Please provide both symbol and code.\nUsage: /setcurrency <symbol> <code>\nExamples:\n/setcurrency $ USD\n/setcurrency T Toman\n/setcurrency IRR Rial")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
currency := parts[0]
|
if len(parts[0]) > 10 || strings.ContainsFunc(parts[0], func(r rune) bool { return r < 32 || r > 126 }) {
|
||||||
if len(parts) >= 2 {
|
h.sendText(ctx, msg.Chat.ID, "Invalid currency symbol. Use a short printable string like $, €, £.")
|
||||||
currency = parts[0] + " " + parts[1]
|
return
|
||||||
}
|
}
|
||||||
|
if len(parts[1]) > 10 || strings.ContainsFunc(parts[1], func(r rune) bool { return r < 32 || r > 126 }) {
|
||||||
|
h.sendText(ctx, msg.Chat.ID, "Invalid currency code.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currency := parts[0] + " " + parts[1]
|
||||||
user, err := h.getOrCreateUser(msg.Chat.ID)
|
user, err := h.getOrCreateUser(msg.Chat.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.sendText(ctx, msg.Chat.ID, "Error loading profile")
|
h.sendText(ctx, msg.Chat.ID, "Error loading profile")
|
||||||
@@ -219,7 +224,7 @@ func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message)
|
|||||||
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Currency set to: %s", currency))
|
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Currency set to: %s", currency))
|
||||||
}
|
}
|
||||||
|
|
||||||
// currencyCallback shows currency info (inline).
|
// currencyCallback shows the currency picker (inline).
|
||||||
func (h *Handler) currencyCallback(ctx context.Context, chatID int64, msgID int, callbackID string) {
|
func (h *Handler) currencyCallback(ctx context.Context, chatID int64, msgID int, callbackID string) {
|
||||||
defer h.answerCb(ctx, callbackID)
|
defer h.answerCb(ctx, callbackID)
|
||||||
user, err := h.getOrCreateUser(chatID)
|
user, err := h.getOrCreateUser(chatID)
|
||||||
@@ -227,12 +232,46 @@ func (h *Handler) currencyCallback(ctx context.Context, chatID int64, msgID int,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
||||||
text := fmt.Sprintf("Currency: %s\n\nUse /setcurrency <symbol> [code] to change it.\nExamples:\n/setcurrency $ USD\n/setcurrency EUR\n/setcurrency Toman", st.Currency)
|
text, kb := buildCurrencyKeyboard(st.Currency)
|
||||||
kb := models.InlineKeyboardMarkup{
|
h.editText(ctx, chatID, msgID, text, &kb)
|
||||||
InlineKeyboard: [][]models.InlineKeyboardButton{
|
|
||||||
{{Text: "Back to Settings", CallbackData: "back_settings"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildCurrencyKeyboard returns the currency preset picker keyboard.
|
||||||
|
func buildCurrencyKeyboard(current string) (string, models.InlineKeyboardMarkup) {
|
||||||
|
presets := []string{"$ USD", "T Toman", "IRR Rial", "€ EUR", "£ GBP"}
|
||||||
|
rows := [][]models.InlineKeyboardButton{}
|
||||||
|
for _, p := range presets {
|
||||||
|
label := p
|
||||||
|
if p == current {
|
||||||
|
label = "> " + label
|
||||||
|
}
|
||||||
|
rows = append(rows, []models.InlineKeyboardButton{
|
||||||
|
{Text: label, CallbackData: "currency_" + p},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rows = append(rows, []models.InlineKeyboardButton{
|
||||||
|
{Text: "Back to Settings", CallbackData: "back_settings"},
|
||||||
|
})
|
||||||
|
return "Select currency:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectCurrency saves the chosen currency preset.
|
||||||
|
func (h *Handler) selectCurrency(ctx context.Context, chatID int64, msgID int, callbackID, currency 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
|
||||||
|
}
|
||||||
|
st.Currency = currency
|
||||||
|
if err := h.DB.UpdateSettings(st); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bt := h.getTodayBreakThreshold(chatID)
|
||||||
|
text, kb := h.buildSettingsKeyboard(user, st, bt)
|
||||||
h.editText(ctx, chatID, msgID, text, &kb)
|
h.editText(ctx, chatID, msgID, text, &kb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,29 +179,39 @@ func (h *Handler) settingsCallback(ctx context.Context, chatID int64, msgID int,
|
|||||||
|
|
||||||
// buildSettingsKeyboard returns the settings menu text and inline keyboard.
|
// buildSettingsKeyboard returns the settings menu text and inline keyboard.
|
||||||
func (h *Handler) buildSettingsKeyboard(user *db.User, st *db.UserSettings, breakThreshold int64) (string, models.InlineKeyboardMarkup) {
|
func (h *Handler) buildSettingsKeyboard(user *db.User, st *db.UserSettings, breakThreshold int64) (string, models.InlineKeyboardMarkup) {
|
||||||
reportStatus := "disabled"
|
reportStatus := "off"
|
||||||
if st.ReportEnabled {
|
if st.ReportEnabled {
|
||||||
reportStatus = "enabled"
|
reportStatus = "on"
|
||||||
}
|
}
|
||||||
rpgStatus := "disabled"
|
rpgStatus := "off"
|
||||||
if st.RPGEnabled {
|
if st.RPGEnabled {
|
||||||
rpgStatus = "enabled"
|
rpgStatus = "on"
|
||||||
}
|
}
|
||||||
leagueStatus := "disabled"
|
leagueStatus := "off"
|
||||||
if st.LeagueOptIn {
|
if st.LeagueOptIn {
|
||||||
leagueStatus = "on"
|
leagueStatus = "on"
|
||||||
}
|
}
|
||||||
rows := [][]models.InlineKeyboardButton{
|
rows := [][]models.InlineKeyboardButton{
|
||||||
{{Text: fmt.Sprintf("Timezone: %s", user.Timezone), CallbackData: "timezone"}},
|
{
|
||||||
{{Text: fmt.Sprintf("Accent: %s", st.ExportAccent), CallbackData: "accent"}},
|
{Text: fmt.Sprintf("Timezone: %s", user.Timezone), CallbackData: "timezone"},
|
||||||
{{Text: fmt.Sprintf("Calendar: %s", user.Calendar), CallbackData: "caltype"}},
|
{Text: fmt.Sprintf("Calendar: %s", user.Calendar), CallbackData: "caltype"},
|
||||||
{{Text: fmt.Sprintf("Report: %s", reportStatus), CallbackData: "reporttoggle"}},
|
},
|
||||||
{{Text: fmt.Sprintf("Report time: %s", st.ReportTime), CallbackData: "reporttime"}},
|
{
|
||||||
{{Text: fmt.Sprintf("Break threshold: %dm", breakThreshold/60), CallbackData: "breakthreshold"}},
|
{Text: fmt.Sprintf("Report: %s", reportStatus), CallbackData: "reporttoggle"},
|
||||||
{{Text: fmt.Sprintf("RPG: %s", rpgStatus), CallbackData: "rpgtoggle"}},
|
{Text: fmt.Sprintf("Report time: %s", st.ReportTime), CallbackData: "reporttime"},
|
||||||
{{Text: fmt.Sprintf("League: %s", leagueStatus), CallbackData: "leaguetoggle"}},
|
},
|
||||||
{{Text: fmt.Sprintf("Currency: %s", st.Currency), CallbackData: "currency"}},
|
{
|
||||||
{{Text: fmt.Sprintf("Rate: %.2f", st.HourlyRate), CallbackData: "setrate"}},
|
{Text: fmt.Sprintf("Break: %dm", breakThreshold/60), CallbackData: "breakthreshold"},
|
||||||
|
{Text: fmt.Sprintf("Accent: %s", st.ExportAccent), CallbackData: "accent"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Text: fmt.Sprintf("RPG: %s", rpgStatus), CallbackData: "rpgtoggle"},
|
||||||
|
{Text: fmt.Sprintf("League: %s", leagueStatus), CallbackData: "leaguetoggle"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Text: fmt.Sprintf("Currency: %s", st.Currency), CallbackData: "currency"},
|
||||||
|
{Text: fmt.Sprintf("Rate: %.2f", st.HourlyRate), CallbackData: "setrate"},
|
||||||
|
},
|
||||||
{{Text: "Back to Menu", CallbackData: "back_menu"}},
|
{{Text: "Back to Menu", CallbackData: "back_menu"}},
|
||||||
}
|
}
|
||||||
return "Settings:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
|
return "Settings:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
|
||||||
|
|||||||
Reference in New Issue
Block a user