package message import ( "bytes" "crypto/tls" "fmt" "io/ioutil" "local/truckstop/config" "local/truckstop/logtr" "net/http" "regexp" "strings" "time" "github.com/matrix-org/gomatrix" ) type Matrix struct { mock bool homeserver string username string token string room string continuation string } func NewSOSMatrix() logtr.SOSer { conf := config.Get().Log.SOSMatrix return newMatrix(conf, "0") } func NewMatrix() Matrix { conf := config.Get().Message.Matrix return newMatrix(conf, GetMatrixContinuation()) } func newMatrix(conf config.Matrix, cont string) Matrix { return Matrix{ homeserver: conf.Homeserver, username: conf.Username, token: conf.Token, room: conf.Room, mock: conf.Mock, continuation: cont, } } func (m Matrix) getclient() (*gomatrix.Client, error) { client, err := gomatrix.NewClient(m.homeserver, m.username, m.token) if err != nil { return nil, err } client.Client = &http.Client{ Timeout: time.Minute, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, }, } return client, nil } func (m Matrix) Continuation() string { return m.continuation } func (m *Matrix) Receive() ([]Message, error) { if m.mock { logtr.Infof("matrix.Receive()") messages := make([]Message, 0) for k := range config.Get().Clients { messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!state nc"}) if k == "bel" { messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!help"}) } if k == "broc" { messages = append(messages, Message{Timestamp: time.Now(), Sender: k, Content: "!available 2148-10-" + fmt.Sprint(time.Now().Unix()%28)}) } } return messages, nil } clients := config.Get().Clients matrixIDs := map[string]struct{}{} for k := range clients { matrixIDs[clients[k].IDs.Matrix] = struct{}{} } if len(matrixIDs) == 0 { return nil, nil } c, err := m.getclient() if err != nil { return nil, err } messages := make([]Message, 0) result, err := c.Messages(m.room, "999999999999999999", m.Continuation(), 'b', 50) if err != nil { return nil, err } logtr.Verbosef("%s => {Start:%s End:%v};; %v, (%d)", m.Continuation(), result.Start, result.End, err, len(result.Chunk)) m.continuation = result.End for _, event := range result.Chunk { logtr.Verbosef("matrix event: %+v", event) if _, ok := matrixIDs[event.Sender]; !ok { continue } switch event.Type { case "m.room.message": b, ok := event.Body() if ok { messages = append(messages, Message{Timestamp: time.Unix(0, event.Timestamp*int64(time.Millisecond)), Sender: event.Sender, Content: strings.TrimSpace(b)}) } } } clientChange := regexp.MustCompile("@[a-z]+$") logtr.Verbosef("rewriting messages based on @abc") for i := range messages { if found := clientChange.FindString(messages[i].Content); found != "" { messages[i].Content = strings.TrimSpace(strings.ReplaceAll(messages[i].Content, found, "")) messages[i].Sender = found[1:] } else { for k, v := range config.Get().Clients { if v.IDs.Matrix == messages[i].Sender { messages[i].Sender = k } } } messages[i].Content = strings.TrimSpace(messages[i].Content) } logtr.Verbosef("rewriting messages based on ! CoMmAnD ...") for i := range messages { if strings.HasPrefix(messages[i].Content, "!") { messages[i].Content = "!" + strings.TrimSpace(messages[i].Content[1:]) splits := strings.Split(messages[i].Content, " ") messages[i].Content = strings.ToLower(splits[0]) + " " + strings.Join(splits, " ") } } return messages, nil } func (m Matrix) Remove(id string) error { if m.mock { logtr.Infof("matrix.Remove(%s)", id) return nil } c, err := m.getclient() if err != nil { return err } _, err = c.RedactEvent(m.room, id, &gomatrix.ReqRedact{Reason: "stale"}) return err } func (m Matrix) Update(id, text string) error { if m.mock { logtr.Infof("matrix.Update(%s)", text) return nil } c, err := m.getclient() if err != nil { return err } type MRelatesTo struct { EventID string `json:"event_id"` RelType string `json:"rel_type"` } type NewContent struct { Body string `json:"body"` MsgType string `json:"msgtype"` } type RelatesToRoomMessage struct { Body string `json:"body"` MsgType string `json:"msgtype"` MRelatesTo MRelatesTo `json:"m.relates_to"` MNewContent NewContent `json:"m.new_content"` } _, err = c.SendMessageEvent(m.room, "m.room.message", RelatesToRoomMessage{ Body: "", MsgType: "m.text", MNewContent: NewContent{ Body: text, MsgType: "m.text", }, MRelatesTo: MRelatesTo{ EventID: id, RelType: "m.replace", }, }) return err } func (m Matrix) Send(text string) error { _, err := m.SendTracked(text) return err } func (m Matrix) SendTracked(text string) (string, error) { if m.mock { logtr.Infof("matrix.SendTracked(%s)", text) return "", nil } c, err := m.getclient() if err != nil { return "", err } resp, err := c.SendText(m.room, text) if err != nil { return "", err } return resp.EventID, nil } func (m Matrix) SendImage(uri string) error { _, err := m.SendImageTracked(uri) return err } func (m Matrix) SendImageTracked(uri string) (string, error) { if m.mock { logtr.Infof("matrix.SendImage(%s)", uri) return "", nil } response, err := http.Get(uri) if err != nil { return "", err } if response.StatusCode != http.StatusOK { b, _ := ioutil.ReadAll(response.Body) response.Body.Close() return "", fmt.Errorf("failed to get %s: (%d) %s", uri, response.StatusCode, b) } b, err := ioutil.ReadAll(response.Body) response.Body.Close() if err != nil { return "", err } c, err := m.getclient() if err != nil { return "", err } mediaUpload, err := c.UploadToContentRepo(bytes.NewReader(b), "image/jpeg", int64(len(b))) if err != nil { return "", err } publicURI := mediaUpload.ContentURI resp, err := c.SendImage(m.room, "img", publicURI) logtr.Verbosef("sent image %s => %s: %+v", uri, publicURI, resp) return resp.EventID, err } func SetMatrixContinuation(continuation string) { db := config.Get().DB() db.Set(getMatrixContinuationKey(), []byte(continuation)) } func GetMatrixContinuation() string { db := config.Get().DB() b, _ := db.Get(getMatrixContinuationKey()) if b == nil { return "0" } return string(b) } func getMatrixContinuationKey() string { return "matrix_continuation" }