Skip to content

Commit

Permalink
start new chat: if enabled, use to create matrix room for new chats
Browse files Browse the repository at this point in the history
Signed-off-by: Sumner Evans <[email protected]>
  • Loading branch information
sumnerevans committed Oct 31, 2023
1 parent 1db2014 commit ec1a9a4
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 34 deletions.
57 changes: 53 additions & 4 deletions chatwoot-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package main
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"image"
_ "image/gif"
Expand Down Expand Up @@ -229,6 +231,11 @@ func handleAttachment(ctx context.Context, roomID id.RoomID, chatwootMessageID i
})
}

type StartNewChatResp struct {
RoomID id.RoomID `json:"room_id,omitempty"`
Error string `json:"error,omitempty"`
}

func HandleMessageCreated(ctx context.Context, mc chatwootapi.MessageCreated) error {
log := zerolog.Ctx(ctx).With().
Str("component", "handle_message_created").
Expand All @@ -243,14 +250,56 @@ func HandleMessageCreated(ctx context.Context, mc chatwootapi.MessageCreated) er

roomID, _, err := stateStore.GetMatrixRoomFromChatwootConversation(ctx, mc.Conversation.ID)
if err != nil {
log.Err(err).Int("conversation_id", mc.Conversation.ID).Msg("no room found for conversation")
return err
if !errors.Is(err, sql.ErrNoRows) {
log.Err(err).Msg("couldn't find room for conversation")
return err
}

if !configuration.StartNewChat.Enable {
log.Err(err).Msg("couldn't find room for conversation")
return err
}

// Create a new room for this conversation using the start new chat
// endpoint.
body, err := json.Marshal(mc.Conversation.Meta.Sender)
if err != nil {
log.Err(err).Msg("failed to marshal sender to JSON")
return err
}
req, err := http.NewRequest(http.MethodPost, configuration.StartNewChat.Endpoint, bytes.NewReader(body))
if err != nil {
log.Err(err).Msg("failed to create request")
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", configuration.StartNewChat.Token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Err(err).Msg("failed to make request")
return err
}
defer resp.Body.Close()
var sncResp StartNewChatResp
err = json.NewDecoder(resp.Body).Decode(&sncResp)
if err != nil {
log.Err(err).Msg("failed to read response body")
return err
}

if resp.StatusCode != http.StatusOK {
log.Warn().Int("status_code", resp.StatusCode).Any("resp", sncResp).Msg("failed to create new chat")
return fmt.Errorf("failed to create new chat: %s", sncResp.Error)
}

log.Info().Str("room_id", sncResp.RoomID.String()).Msg("created new chat for conversation")
roomID = sncResp.RoomID
}
log = log.With().Str("room_id", roomID.String()).Logger()
ctx = log.WithContext(ctx)

// Acquire the lock, so that we don't have race conditions with the
// matrix handler.
// Acquire the lock, so that we don't have race conditions with the matrix
// handler.
if _, found := roomSendlocks[roomID]; !found {
log.Debug().Msg("creating send lock")
roomSendlocks[roomID] = &sync.Mutex{}
Expand Down
68 changes: 41 additions & 27 deletions chatwootapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,19 @@ func (api *ChatwootAPI) CreateContact(ctx context.Context, userID id.UserID) (in
Str("user_id", userID.String()).
Logger()

log.Info().Msg("Creating contact")
name := userID.String()
if userID.Homeserver() == "beeper.local" {
decoded, err := id.DecodeUserLocalpart(strings.TrimPrefix(userID.Localpart(), "imessagego_1."))
if err == nil {
name = decoded
}
}

log.Info().Str("name", name).Msg("Creating contact")
payload := CreateContactPayload{
InboxID: api.InboxID,
Name: userID.String(),
Identifier: userID.String(),
Name: name,
Identifier: name,
}
jsonValue, _ := json.Marshal(payload)
req, err := http.NewRequest(http.MethodPost, api.MakeUri("contacts"), bytes.NewBuffer(jsonValue))
Expand All @@ -106,25 +114,37 @@ func (api *ChatwootAPI) CreateContact(ctx context.Context, userID id.UserID) (in
return 0, fmt.Errorf("POST contacts returned non-200 status code: %d", resp.StatusCode)
}

decoder := json.NewDecoder(resp.Body)
var contactPayload ContactPayload
err = decoder.Decode(&contactPayload)
if err != nil {
if err := json.NewDecoder(resp.Body).Decode(&contactPayload); err != nil {
return 0, err
}

log.Debug().Interface("contact_payload", contactPayload).Msg("Got contact payload")
return contactPayload.Payload.Contact.ID, nil
}

func (api *ChatwootAPI) ContactIDForMXID(userID id.UserID) (int, error) {
func (api *ChatwootAPI) ContactIDForMXID(ctx context.Context, userID id.UserID) (int, error) {
log := zerolog.Ctx(ctx)
query := userID.String()
if userID.Homeserver() == "beeper.local" {
// Special handling for bridged iMessages.
if strings.HasPrefix(userID.Localpart(), "imessagego_1.") {
decoded, err := id.DecodeUserLocalpart(strings.TrimPrefix(userID.Localpart(), "imessagego_1."))
if err == nil {
query = decoded
}
}
}

log.Info().Str("query", query).Msg("Searching for contact")

req, err := http.NewRequest(http.MethodGet, api.MakeUri("contacts/search"), nil)
if err != nil {
return 0, err
}

q := req.URL.Query()
q.Add("q", userID.String())
q.Add("q", query)
req.URL.RawQuery = q.Encode()

resp, err := api.DoRequest(req)
Expand All @@ -135,19 +155,22 @@ func (api *ChatwootAPI) ContactIDForMXID(userID id.UserID) (int, error) {
return 0, fmt.Errorf("GET contacts/search returned non-200 status code: %d", resp.StatusCode)
}

decoder := json.NewDecoder(resp.Body)
var contactsPayload ContactsPayload
err = decoder.Decode(&contactsPayload)
if err != nil {
if err := json.NewDecoder(resp.Body).Decode(&contactsPayload); err != nil {
return 0, err
}

for _, contact := range contactsPayload.Payload {
if contact.Identifier == userID.String() {
if contact.Identifier == query {
return contact.ID, nil
} else if contact.Email == query {
return contact.ID, nil
} else if contact.PhoneNumber == query {
return contact.ID, nil
}
}

return 0, fmt.Errorf("couldn't find user with user ID %s", userID)
return 0, fmt.Errorf("couldn't find user with user ID %s", query)
}

func (api *ChatwootAPI) GetChatwootConversation(conversationID int) (*Conversation, error) {
Expand All @@ -164,13 +187,9 @@ func (api *ChatwootAPI) GetChatwootConversation(conversationID int) (*Conversati
return nil, fmt.Errorf("GET conversations/%d returned non-200 status code: %d", conversationID, resp.StatusCode)
}

decoder := json.NewDecoder(resp.Body)
var conversation Conversation
err = decoder.Decode(&conversation)
if err != nil {
return nil, err
}
return &conversation, nil
err = json.NewDecoder(resp.Body).Decode(&conversation)
return &conversation, err
}

func (api *ChatwootAPI) CreateConversation(sourceID string, contactID int, additionalAttrs map[string]string) (*Conversation, error) {
Expand All @@ -196,13 +215,9 @@ func (api *ChatwootAPI) CreateConversation(sourceID string, contactID int, addit
return nil, fmt.Errorf("POST conversations returned non-200 status code: %d: %s", resp.StatusCode, string(content))
}

decoder := json.NewDecoder(resp.Body)
var conversation Conversation
err = decoder.Decode(&conversation)
if err != nil {
return nil, err
}
return &conversation, nil
err = json.NewDecoder(resp.Body).Decode(&conversation)
return &conversation, err
}

func (api *ChatwootAPI) GetConversationLabels(conversationID int) ([]string, error) {
Expand All @@ -220,9 +235,8 @@ func (api *ChatwootAPI) GetConversationLabels(conversationID int) ([]string, err
return nil, fmt.Errorf("POST conversations returned non-200 status code: %d: %s", resp.StatusCode, string(content))
}

decoder := json.NewDecoder(resp.Body)
var labels ConversationLabelsPayload
err = decoder.Decode(&labels)
err = json.NewDecoder(resp.Body).Decode(&labels)
return labels.Payload, err
}

Expand Down
6 changes: 4 additions & 2 deletions chatwootapi/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package chatwootapi

// Contact
type Contact struct {
ID int `json:"id"`
Identifier string `json:"identifier"`
ID int `json:"id"`
Identifier string `json:"identifier"`
PhoneNumber string `json:"phone_number,omitempty"`
Email string `json:"email,omitempty"`
}

type ContactsPayload struct {
Expand Down
2 changes: 1 addition & 1 deletion matrix-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func createChatwootConversation(ctx context.Context, roomID id.RoomID, contactMX
return conversationID, nil
}

contactID, err := chatwootAPI.ContactIDForMXID(contactMXID)
contactID, err := chatwootAPI.ContactIDForMXID(ctx, contactMXID)
if err != nil {
log.Warn().Err(err).Msg("contact ID not found for user, will attempt to create one")

Expand Down

0 comments on commit ec1a9a4

Please sign in to comment.