package main import ( "encoding/json" "errors" "fmt" "regexp" "strconv" "strings" "time" ) var ( ErrIrrelevantMessage = errors.New("message isnt relevant to spoc bot vr") ) type Message struct { ID string TS uint64 Source string Channel string Thread string EventName string Event string Plaintext string Asset string Resolved bool Datacenter string } func (m Message) Empty() bool { return m == (Message{}) } func (m Message) Time() time.Time { return time.Unix(int64(m.TS), 0) } func (m Message) Serialize() []byte { b, err := json.Marshal(m) if err != nil { panic(err) } return b } func MustDeserialize(b []byte) Message { m, err := Deserialize(b) if err != nil { panic(err) } return m } func Deserialize(b []byte) (Message, error) { var m Message err := json.Unmarshal(b, &m) return m, err } type ( slackMessage struct { slackEvent Type string TS uint64 `json:"event_time"` Event slackEvent MessageTS string `json:"ts"` } slackEvent struct { ID string `json:"event_ts"` Channel string // rewrites Nested *slackEvent `json:"message"` PreviousMessage *slackEvent `json:"previous_message"` // human ParentID string `json:"thread_ts"` Text string Blocks []slackBlock // bot Bot slackBot `json:"bot_profile"` Attachments []slackAttachment } slackBlock struct { Elements []slackElement } slackElement struct { Elements []slackElement RichText string `json:"text"` } slackBot struct { Name string } slackAttachment struct { Color string Title string Text string Fields []slackField Actions []slackAction } slackField struct { Value string Title string } slackAction struct{} ) func ParseSlack(b []byte, assetPattern, datacenterPattern, eventNamePattern string) (Message, error) { return ParseSlackFromChannel(b, assetPattern, datacenterPattern, eventNamePattern, "") } func ParseSlackFromChannel(b []byte, assetPattern, datacenterPattern, eventNamePattern string, ch string) (Message, error) { m, err := parseSlackJSON(b, ch) if err != nil { return Message{}, err } for pattern, ptr := range map[string]*string{ assetPattern: &m.Asset, datacenterPattern: &m.Datacenter, eventNamePattern: &m.EventName, } { r := regexp.MustCompile(pattern) parsed := r.FindString(*ptr) for i, name := range r.SubexpNames() { if i > 0 && name != "" { parsed = r.FindStringSubmatch(*ptr)[i] } } *ptr = parsed } return m, nil } func parseSlackJSON(b []byte, ch string) (Message, error) { s, err := _parseSlackJSON(b) if err != nil { return Message{}, err } if ch != "" { s.Event.Channel = ch } if s.Event.Bot.Name != "" { if len(s.Event.Attachments) == 0 { return Message{}, ErrIrrelevantMessage } else if !strings.Contains(s.Event.Attachments[0].Title, ": Firing: ") { return Message{}, ErrIrrelevantMessage } var tagsField string for _, field := range s.Event.Attachments[0].Fields { if field.Title == "Tags" { tagsField = field.Value } } return Message{ ID: fmt.Sprintf("%s/%v", s.Event.ID, s.TS), TS: s.TS, Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ID, ".", "")), Channel: s.Event.Channel, Thread: s.Event.ID, EventName: strings.Split(s.Event.Attachments[0].Title, ": Firing: ")[1], Event: strings.TrimPrefix(strings.Split(s.Event.Attachments[0].Title, ":")[0], "#"), Plaintext: s.Event.Attachments[0].Text, Asset: s.Event.Attachments[0].Text, Resolved: !strings.HasPrefix(s.Event.Attachments[0].Color, "F"), Datacenter: tagsField, }, nil } if s.Event.ParentID == "" { return Message{}, ErrIrrelevantMessage } return Message{ ID: fmt.Sprintf("%s/%v", s.Event.ParentID, s.TS), TS: s.TS, Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ParentID, ".", "")), Channel: s.Event.Channel, Thread: s.Event.ParentID, EventName: "", Event: "", Plaintext: s.Event.Text, Asset: "", Datacenter: "", }, nil } func _parseSlackJSON(b []byte) (slackMessage, error) { var result slackMessage err := json.Unmarshal(b, &result) switch result.Type { case "message": result.Event = result.slackEvent result.TS, _ = strconv.ParseUint(strings.Split(result.MessageTS, ".")[0], 10, 64) result.Event.ID = result.MessageTS } if result.Event.Nested != nil && !result.Event.Nested.Empty() { result.Event.Blocks = result.Event.Nested.Blocks result.Event.Bot = result.Event.Nested.Bot result.Event.Attachments = result.Event.Nested.Attachments result.Event.Nested = nil } if result.Event.PreviousMessage != nil { if result.Event.PreviousMessage.ID != "" { result.Event.ID = result.Event.PreviousMessage.ID } result.Event.PreviousMessage = nil } return result, err } func (this slackEvent) Empty() bool { return fmt.Sprintf("%+v", this) == fmt.Sprintf("%+v", slackEvent{}) }