| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package telegram
- import (
- "fmt"
- "strings"
- "time"
- "git3.techno-world.net/lrosales/broad-announce/internal/alert"
- )
- // Command is a parsed bot command. Exactly one of the fields
- // is non-nil per command. The handler dispatches on which
- // one is set.
- type Command struct {
- Raw string
- Start *StartCmd
- Subscribe *SubscribeCmd
- Unsubscribe *UnsubscribeCmd
- Preferences bool
- Status *StatusCmd
- Mute *MuteCmd
- Unmute bool
- Unknown string
- }
- // StartCmd is /start <invite_code>. Empty invite_code is OK
- // at parse time; the handler returns a friendly "please pass
- // your invite code" reply.
- type StartCmd struct {
- InviteCode string
- }
- // SubscribeCmd is /subscribe <source_id> [<min_severity>].
- type SubscribeCmd struct {
- SourceID string
- MinSeverity string
- }
- // UnsubscribeCmd is /unsubscribe <source_id>.
- type UnsubscribeCmd struct {
- SourceID string
- }
- // StatusCmd is /status [N] with N defaulting to 5.
- type StatusCmd struct {
- Limit int
- }
- // MuteCmd is /mute <duration>. We accept:
- // /mute 2h
- // /mute 30m
- // /mute 90s
- // /mute until 18:00 (HH:MM today, tomorrow if past)
- type MuteCmd struct {
- Duration time.Duration // for /mute 2h
- Until time.Time // for /mute until HH:MM
- }
- // Parse turns a raw message text into a Command. The
- // message MUST start with '/'. Text without a leading slash
- // is returned as a Command{Unknown: <text>}; the handler
- // treats Unknown as "ignore, don't reply".
- func Parse(text string) Command {
- out := Command{Raw: text}
- t := strings.TrimSpace(text)
- if t == "" {
- out.Unknown = ""
- return out
- }
- if !strings.HasPrefix(t, "/") {
- out.Unknown = t
- return out
- }
- // Strip the leading '/' and split into parts. Telegram
- // commands are space-separated and may include @<bot>
- // suffix (e.g. /start@acme_x_bot).
- parts := strings.Fields(strings.TrimPrefix(t, "/"))
- if len(parts) == 0 {
- out.Unknown = t
- return out
- }
- cmd := parts[0]
- if at := strings.Index(cmd, "@"); at >= 0 {
- cmd = cmd[:at]
- }
- rest := parts[1:]
- switch strings.ToLower(cmd) {
- case "start":
- c := StartCmd{}
- if len(rest) >= 1 {
- c.InviteCode = rest[0]
- }
- out.Start = &c
- case "subscribe":
- if len(rest) < 1 {
- out.Unknown = "subscribe requires <source_id> [<min_severity>]"
- return out
- }
- c := SubscribeCmd{SourceID: rest[0]}
- if len(rest) >= 2 {
- c.MinSeverity = rest[1]
- } else {
- c.MinSeverity = "info" // default
- }
- if _, ok := alert.ValidSeverities[alert.Severity(c.MinSeverity)]; !ok {
- out.Unknown = fmt.Sprintf("min_severity must be one of info|warning|critical|inminent_colapse, got %q", c.MinSeverity)
- return out
- }
- out.Subscribe = &c
- case "unsubscribe":
- if len(rest) < 1 {
- out.Unknown = "unsubscribe requires <source_id>"
- return out
- }
- out.Unsubscribe = &UnsubscribeCmd{SourceID: rest[0]}
- case "preferences":
- out.Preferences = true
- case "status":
- c := StatusCmd{Limit: 5}
- if len(rest) >= 1 {
- var n int
- if _, err := fmt.Sscanf(rest[0], "%d", &n); err == nil && n > 0 && n <= 50 {
- c.Limit = n
- }
- }
- out.Status = &c
- case "mute":
- if len(rest) < 1 {
- out.Unknown = "mute requires <duration> e.g. 2h, 30m, 90s, or 'until 18:00'"
- return out
- }
- if rest[0] == "until" && len(rest) >= 2 {
- now := time.Now().UTC()
- t, err := time.ParseInLocation("15:04", rest[1], time.UTC)
- if err != nil {
- out.Unknown = fmt.Sprintf("can't parse time %q (expected HH:MM): %v", rest[1], err)
- return out
- }
- until := time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), 0, 0, time.UTC)
- if until.Before(now) {
- until = until.Add(24 * time.Hour) // tomorrow
- }
- out.Mute = &MuteCmd{Until: until}
- return out
- }
- d, err := time.ParseDuration(rest[0])
- if err != nil {
- out.Unknown = fmt.Sprintf("can't parse duration %q (e.g. 2h, 30m, 90s): %v", rest[0], err)
- return out
- }
- out.Mute = &MuteCmd{Duration: d}
- case "unmute":
- out.Unmute = true
- default:
- out.Unknown = "unknown command: /" + cmd
- }
- return out
- }
|