package tldr import ( "fmt" "regexp" "strconv" "strings" "time" ) const discordEpochMs = 1420070400000 var sinceRe = regexp.MustCompile(`^(\d+)([mhd])$`) // ParseSince turns "30m", "6h" or "2d" into a duration, clamped to // [1m, maxWindow]. time.ParseDuration has no day unit, hence the custom parser. func ParseSince(s string) (time.Duration, error) { m := sinceRe.FindStringSubmatch(strings.ToLower(strings.TrimSpace(s))) if m == nil { return 0, fmt.Errorf("couldn't read %q, use something like 30m, 6h or 2d", s) } n, err := strconv.Atoi(m[1]) if err != nil || n <= 0 { return 0, fmt.Errorf("couldn't read %q, use something like 30m, 6h or 2d", s) } unit := map[string]time.Duration{"m": time.Minute, "h": time.Hour, "d": 24 * time.Hour}[m[2]] if n > int(maxWindow/unit) { return maxWindow, nil } return max(time.Duration(n)*unit, time.Minute), nil } // SnowflakeFromTime returns the smallest Discord snowflake for t, usable as // an "after" cursor to mean "messages posted after this moment". func SnowflakeFromTime(t time.Time) string { ms := max(t.UnixMilli()-discordEpochMs, 0) return strconv.FormatInt(ms<<22, 10) } // CompareIDs compares two snowflakes numerically (-1, 0, 1). func CompareIDs(a, b string) int { ai, aerr := strconv.ParseUint(a, 10, 64) bi, berr := strconv.ParseUint(b, 10, 64) if aerr != nil || berr != nil { return strings.Compare(a, b) } switch { case ai < bi: return -1 case ai > bi: return 1 } return 0 }