Files
store-scraper-bot/internal/util/table.go
T
tiennm99 e695d9b223 feat: port logic from Java original to align with current Java behavior
- Switch Telegram parse mode to HTML to match Java client.
- Rename command identifiers to Java's: delgroup/delapple/delgoogle/
  checkappscore/rawappleapp/rawgoogleapp.
- Move admin singleton to "common" collection at _id="admin".
- Store group _id as string-of-int64 to match Java AbstractModel schema.
- Add `class` discriminator field on persisted models.
- Expand AppleAppResponse (42 fields) and GoogleAppResponse (63 fields,
  with nested Category/Feature) to match Java records.
- Add AppleAppRequest (track id or bundle id) / GoogleAppRequest types.
- Implement Raw* commands as Telegram document attachments instead of
  truncated text.
- Group-authorization gate (admin.HasGroup(chatId)) on non-admin commands.
- Cache TTL via millis timestamp; round score to 1 decimal; weekend silent
  send in scheduler.
- Match Java table renderer: " │ " separators, "─┼─" rows every 5 lines.
- Prefer MONGODB_CONNECTION_STRING env (Java parity); auto-extract DB
  name from URI with fallback to "store-scraper-bot".

Note: This is an AI-assisted port (not human-verified). Behavior parity
with the Java implementation has not been tested end-to-end; treat as a
starting point.
2026-04-25 20:26:25 +07:00

96 lines
2.0 KiB
Go

package util
import (
"fmt"
"strings"
)
// BuildTable mirrors Java bot/table/Table.java:
// - left-aligned columns padded to max(header, cell) width
// - "│" column separator
// - row separator inserted every 5 rows using "─" cells joined by "─┼─"
//
// Output is intended to be wrapped in <pre> for Telegram HTML rendering.
func BuildTable(headers []string, rows [][]string) string {
widths := computeWidths(headers, rows)
var sb strings.Builder
writeRow(&sb, headers, widths)
sb.WriteString("\n")
writeSeparator(&sb, widths)
for i, row := range rows {
sb.WriteString("\n")
if i > 0 && i%5 == 0 {
writeSeparator(&sb, widths)
sb.WriteString("\n")
}
writeRow(&sb, row, widths)
}
return sb.String()
}
func computeWidths(headers []string, rows [][]string) []int {
widths := make([]int, len(headers))
for i, h := range headers {
widths[i] = len(h)
}
for _, row := range rows {
for i, cell := range row {
if i < len(widths) && len(cell) > widths[i] {
widths[i] = len(cell)
}
}
}
return widths
}
func writeRow(sb *strings.Builder, cells []string, widths []int) {
for i, w := range widths {
var cell string
if i < len(cells) {
cell = cells[i]
}
sb.WriteString(padRight(cell, w))
if i < len(widths)-1 {
sb.WriteString(" │ ")
}
}
}
func writeSeparator(sb *strings.Builder, widths []int) {
for i, w := range widths {
sb.WriteString(strings.Repeat("─", w))
if i < len(widths)-1 {
sb.WriteString("─┼─")
}
}
}
func padRight(s string, length int) string {
if len(s) >= length {
return s
}
return s + strings.Repeat(" ", length-len(s))
}
func TruncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
return s[:maxLen]
}
return s[:maxLen-3] + "..."
}
func FormatNumber(n int64) string {
if n >= 1_000_000 {
return fmt.Sprintf("%.1fM", float64(n)/1_000_000)
}
if n >= 1_000 {
return fmt.Sprintf("%.1fK", float64(n)/1_000)
}
return fmt.Sprintf("%d", n)
}