Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/bb api helper functions #155

Merged
merged 3 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 73 additions & 70 deletions imessage/bluebubbles/api.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package bluebubbles

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
Expand Down Expand Up @@ -254,83 +256,84 @@ func (bb *blueBubbles) GetMessage(guid string) (resp *imessage.Message, err erro
return nil, ErrNotImplemented
}

func (bb *blueBubbles) GetChatsWithMessagesAfter(minDate time.Time) (resp []imessage.ChatIdentifier, err error) {
url := bb.bridge.GetConnectorConfig().BlueBubblesURL + "/api/v1/chat/query?password=" + bb.bridge.GetConnectorConfig().BlueBubblesPassword
method := "POST"
func (bb *blueBubbles) apiUrl(path string) string {
u, err := url.Parse(bb.bridge.GetConnectorConfig().BlueBubblesURL)
if err != nil {
bb.log.Error().Err(err).Msg("Error parsing BlueBubbles URL")
// TODO error handling for bad config
return ""
}

limit := 1000
offset := 0

for {
payload := fmt.Sprintf(`{
"limit": %d,
"offset": %d,
"with": [
"lastMessage",
"sms"
],
"sort": "lastmessage"
}`, limit, offset)

client := &http.Client{}
req, err := http.NewRequest(method, url, strings.NewReader(payload))
if err != nil {
return nil, err
}
u.Path = path

res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
q := u.Query()
q.Add("password", bb.bridge.GetConnectorConfig().BlueBubblesPassword)
u.RawQuery = q.Encode()

var result map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return nil, err
}
url := u.String()

chatsData, ok := result["data"].([]interface{})
if !ok {
return nil, errors.New("invalid response format")
}
return url
}

if len(chatsData) == 0 {
// No more data, break out of the loop
break
}
func (bb *blueBubbles) apiPost(path string, payload interface{}, target interface{}) (err error) {
url := bb.apiUrl(path)

for _, chat := range chatsData {
chatMap, ok := chat.(map[string]interface{})
if !ok {
return nil, errors.New("invalid chat format in response")
}

properties, ok := chatMap["properties"].([]interface{})
if !ok || len(properties) == 0 {
return nil, errors.New("invalid properties format in chat")
}

lsmdStr, ok := properties[0].(map[string]interface{})["LSMD"].(string)
if !ok {
return nil, errors.New("invalid LSMD format in chat properties")
}

lsmd, err := time.Parse(time.RFC3339, lsmdStr)
if err != nil {
return nil, err
}

if lsmd.After(minDate) {
// The last sent message date is after the minDate, add this chat to the return
resp = append(resp, imessage.ChatIdentifier{
ChatGUID: fmt.Sprintf("%v", chatMap["guid"]),
ThreadID: fmt.Sprintf("%v", chatMap["chatIdentifier"]),
})
}
}
bb.log.Info().Str("url", url).Msg("Making POST request")

payloadJSON, err := json.Marshal(payload)
if err != nil {
bb.log.Error().Err(err).Msg("Error marshalling payload")
return err
}

response, err := http.Post(url, "application/json", bytes.NewBuffer(payloadJSON))
if err != nil {
bb.log.Error().Err(err).Msg("Error making POST request")
return err
}
defer response.Body.Close()

responseBody, err := io.ReadAll(response.Body)
if err != nil {
bb.log.Error().Err(err).Msg("Error reading response body")
return err
}

if err := json.Unmarshal(responseBody, target); err != nil {
bb.log.Error().Err(err).Msg("Error unmarshalling response body")
return err
}

return nil
}

func (bb *blueBubbles) GetChatsWithMessagesAfter(minDate time.Time) (resp []imessage.ChatIdentifier, err error) {
// TODO: find out how to make queries based on minDate and the bluebubbles API
// TODO: pagination
limit := int64(5)
offset := int64(0)

request := ChatQueryRequest{
Limit: limit,
Offset: offset,
With: []ChatQueryWith{
ChatQueryWithLastMessage,
ChatQueryWithSMS,
},
Sort: QuerySortLastMessage,
}
var response ChatQueryResponse

err = bb.apiPost("/api/v1/chat/query", request, &response)
if err != nil {
return nil, err
}

// Update offset for the next request
offset += limit
for _, chat := range response.Data {
resp = append(resp, imessage.ChatIdentifier{
ChatGUID: chat.GUID,
ThreadID: chat.GroupId,
})
}

return resp, nil
Expand Down
43 changes: 43 additions & 0 deletions imessage/bluebubbles/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package bluebubbles

type PageMetadata struct {
Count int64 `json:"count"`
Total int64 `json:"total"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
}

type QuerySort string

const (
QuerySortLastMessage QuerySort = "lastmessage"
)

type ChatQueryRequest struct {
// TODO Other Fields
Limit int64 `json:"limit"`
Offset int64 `json:"offset"`
With []ChatQueryWith `json:"with"`
Sort QuerySort `json:"sort"`
}

type ChatQueryWith string

const (
ChatQueryWithSMS ChatQueryWith = "sms"
ChatQueryWithLastMessage ChatQueryWith = "lastMessage"
)

type ChatQueryResponse struct {
Status int64 `json:"status"`
Message string `json:"message"`
Data []Chat `json:"data"`
Metadata PageMetadata `json:"metadata"`
}

type Chat struct {
// TODO How to get timestamp
GUID string `json:"guid"`
ChatIdentifier string `json:"chatIdentifier"`
GroupId string `json:"groupId"`
}