From 8bb04b5ac1a9e1c8d485c41e54629c33b8c345dd Mon Sep 17 00:00:00 2001 From: louis Date: Tue, 21 May 2024 21:01:44 +0200 Subject: [PATCH] Apply review suggestions --- internal/api/tickers.go | 8 +- internal/bridge/signal_group.go | 72 ++++++++- internal/config/config.go | 3 +- internal/signal/signal.go | 272 ++++++++------------------------ internal/storage/message.go | 2 +- 5 files changed, 141 insertions(+), 216 deletions(-) diff --git a/internal/api/tickers.go b/internal/api/tickers.go index b2204c24..9dd14bdf 100644 --- a/internal/api/tickers.go +++ b/internal/api/tickers.go @@ -307,7 +307,8 @@ func (h *handler) PutTickerSignalGroup(c *gin.Context) { if body.GroupName != "" && body.GroupDescription != "" { ticker.SignalGroup.GroupName = body.GroupName ticker.SignalGroup.GroupDescription = body.GroupDescription - err = signal.CreateOrUpdateGroup(&ticker.SignalGroup, h.config) + groupClient := signal.NewGroupClient(h.config) + err = groupClient.CreateOrUpdateGroup(&ticker.SignalGroup) if err != nil { log.WithError(err).Error("failed to create or update group") c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.SignalGroupError)) @@ -332,11 +333,10 @@ func (h *handler) DeleteTickerSignalGroup(c *gin.Context) { return } - err = signal.QuitGroup(h.config, ticker.SignalGroup.GroupID) + groupClient := signal.NewGroupClient(h.config) + err = groupClient.QuitGroup(ticker.SignalGroup.GroupID) if err != nil { log.WithError(err).Error("failed to quit group") - // c.JSON(http.StatusNotFound, response.ErrorResponse(response.CodeDefault, response.SignalGroupError)) - // return } ticker.SignalGroup.Reset() diff --git a/internal/bridge/signal_group.go b/internal/bridge/signal_group.go index 499cf2b8..e8841dc5 100644 --- a/internal/bridge/signal_group.go +++ b/internal/bridge/signal_group.go @@ -1,6 +1,12 @@ package bridge import ( + "context" + "encoding/base64" + "errors" + "fmt" + "os" + "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/signal" "github.com/systemli/ticker/internal/storage" @@ -11,29 +17,87 @@ type SignalGroupBridge struct { storage storage.Storage } +type SignalGroupResponse struct { + Timestamp int `json:"timestamp"` +} + func (sb *SignalGroupBridge) Send(ticker storage.Ticker, message *storage.Message) error { if !sb.config.SignalGroup.Enabled() || !ticker.SignalGroup.Connected() || !ticker.SignalGroup.Active { return nil } - err := signal.SendGroupMessage(sb.config, sb.storage, ticker.SignalGroup.GroupID, message) + ctx := context.Background() + client := signal.Client(sb.config) + + var attachments []string + if len(message.Attachments) > 0 { + for _, attachment := range message.Attachments { + upload, err := sb.storage.FindUploadByUUID(attachment.UUID) + if err != nil { + log.WithError(err).Error("failed to find upload") + continue + } + + fileContent, err := os.ReadFile(upload.FullPath(sb.config.Upload.Path)) + if err != nil { + log.WithError(err).Error("failed to read file") + continue + } + fileBase64 := base64.StdEncoding.EncodeToString(fileContent) + aString := fmt.Sprintf("data:%s;filename=%s;base64,%s", upload.ContentType, upload.FileName, fileBase64) + attachments = append(attachments, aString) + } + } + + params := struct { + Account string `json:"account"` + GroupID string `json:"group-id"` + Message string `json:"message"` + Attachment []string `json:"attachment"` + }{ + Account: sb.config.SignalGroup.Account, + GroupID: ticker.SignalGroup.GroupID, + Message: message.Text, + Attachment: attachments, + } + + var response SignalGroupResponse + err := client.CallFor(ctx, &response, "send", ¶ms) if err != nil { return err } + if response.Timestamp == 0 { + return errors.New("SignalGroup Bridge: No timestamp in send response") + } + + message.SignalGroup = storage.SignalGroupMeta{ + Timestamp: response.Timestamp, + } return nil } func (sb *SignalGroupBridge) Delete(ticker storage.Ticker, message *storage.Message) error { - if !sb.config.SignalGroup.Enabled() || !ticker.SignalGroup.Connected() || !ticker.SignalGroup.Active || message.SignalGroup.Timestamp == nil { + if !sb.config.SignalGroup.Enabled() || !ticker.SignalGroup.Connected() || !ticker.SignalGroup.Active || message.SignalGroup.Timestamp != 0 { return nil } - err := signal.DeleteMessage(sb.config, ticker.SignalGroup.GroupID, message) + client := signal.Client(sb.config) + params := struct { + Account string `json:"account"` + GroupID string `json:"group-id"` + TargetTimestamp int `json:"target-timestamp"` + }{ + Account: sb.config.SignalGroup.Account, + GroupID: ticker.SignalGroup.GroupID, + TargetTimestamp: message.SignalGroup.Timestamp, + } + + var response SignalGroupResponse + err := client.CallFor(context.Background(), &response, "remoteDelete", ¶ms) if err != nil { return err } return nil - } diff --git a/internal/config/config.go b/internal/config/config.go index 99970f23..77478870 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -38,8 +38,7 @@ type Telegram struct { type SignalGroup struct { ApiUrl string `yaml:"api_url"` - ApiUser string `yaml:"api_user"` - ApiPass string `yaml:"api_pass"` + Avatar string `yaml:"avatar"` Account string } diff --git a/internal/signal/signal.go b/internal/signal/signal.go index 11f5b52a..213dad0d 100644 --- a/internal/signal/signal.go +++ b/internal/signal/signal.go @@ -2,63 +2,13 @@ package signal import ( "context" - "encoding/base64" "errors" - "fmt" - "os" - "github.com/sirupsen/logrus" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" "github.com/ybbus/jsonrpc/v3" ) -var log = logrus.WithField("package", "signal") - -type createGroupParams struct { - Account string `json:"account"` - Name string `json:"name"` - Description string `json:"description"` - Avatar string `json:"avatar"` - Link string `json:"link"` - SetPermissionAddMember string `json:"setPermissionAddMember"` - SetPermissionEditDetails string `json:"setPermissionEditDetails"` - SetPermissionSendMessages string `json:"setPermissionSendMessages"` - Expiration int `json:"expiration"` -} - -type CreateGroupResponse struct { - GroupID string `json:"groupId"` - Timestamp int `json:"timestamp"` -} - -type updateGroupParams struct { - Account string `json:"account"` - GroupID string `json:"group-id"` - Name string `json:"name"` - Description string `json:"description"` - Avatar string `json:"avatar"` - Link string `json:"link"` - SetPermissionAddMember string `json:"setPermissionAddMember"` - SetPermissionEditDetails string `json:"setPermissionEditDetails"` - SetPermissionSendMessages string `json:"setPermissionSendMessages"` - Expiration int `json:"expiration"` -} - -type UpdateGroupResponse struct { - Timestamp int `json:"timestamp"` -} - -type QuitGroupParams struct { - Account string `json:"account"` - GroupID string `json:"group-id"` - Delete bool `json:"delete"` -} - -type ListGroupsParams struct { - Account string `json:"account"` -} - type ListGroupsResponseGroup struct { GroupID string `json:"id"` Name string `json:"name"` @@ -66,81 +16,66 @@ type ListGroupsResponseGroup struct { GroupInviteLink string `json:"groupInviteLink"` } -type SendParams struct { - Account string `json:"account"` - GroupID string `json:"group-id"` - Message string `json:"message"` - Attachment []string `json:"attachment"` +type GroupClient struct { + cfg config.Config + client jsonrpc.RPCClient } -type SendResponse struct { - Timestamp *int `json:"timestamp"` -} +func NewGroupClient(cfg config.Config) *GroupClient { + client := Client(cfg) -type DeleteParams struct { - Account string `json:"account"` - GroupID string `json:"group-id"` - TargetTimestamp *int `json:"target-timestamp"` + return &GroupClient{cfg, client} } -func CreateOrUpdateGroup(ts *storage.TickerSignalGroup, config config.Config) error { - ctx := context.Background() - client := rpcClient(config) +func (gc *GroupClient) CreateOrUpdateGroup(ts *storage.TickerSignalGroup) error { + params := struct { + Account string `json:"account"` + GroupID string `json:"group-id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar"` + Link string `json:"link"` + SetPermissionAddMember string `json:"setPermissionAddMember"` + SetPermissionEditDetails string `json:"setPermissionEditDetails"` + SetPermissionSendMessages string `json:"setPermissionSendMessages"` + Expiration int `json:"expiration"` + }{ + Account: gc.cfg.SignalGroup.Account, + Name: ts.GroupName, + Description: ts.GroupDescription, + Avatar: gc.cfg.SignalGroup.Avatar, + Link: "enabled", + SetPermissionAddMember: "every-member", + SetPermissionEditDetails: "only-admins", + SetPermissionSendMessages: "only-admins", + Expiration: 86400, + } - var err error + var response struct { + GroupID string `json:"groupId"` + Timestamp int `json:"timestamp"` + } + if ts.GroupID != "" { + params.GroupID = ts.GroupID + } + err := gc.client.CallFor(context.Background(), &response, "updateGroup", ¶ms) + if err != nil { + return err + } if ts.GroupID == "" { - // Create new group - var response *CreateGroupResponse - params := createGroupParams{ - Account: config.SignalGroup.Account, - Name: ts.GroupName, - Description: ts.GroupDescription, - Avatar: "/var/lib/signal-cli/data/ticker.png", - Link: "enabled", - SetPermissionAddMember: "every-member", - SetPermissionEditDetails: "only-admins", - SetPermissionSendMessages: "only-admins", - Expiration: 86400, - } - err = client.CallFor(ctx, &response, "updateGroup", ¶ms) - if err != nil { - return err - } - if response.GroupID == "" { - return errors.New("SignalGroup Bridge: No group ID in create group response") - } - log.WithField("groupId", response.GroupID).Debug("Created group") ts.GroupID = response.GroupID - } else { - // Update existing group - params := updateGroupParams{ - Account: config.SignalGroup.Account, - GroupID: ts.GroupID, - Name: ts.GroupName, - Description: ts.GroupDescription, - Avatar: "/var/lib/signal-cli/data/ticker.png", - Link: "enabled", - SetPermissionAddMember: "every-member", - SetPermissionEditDetails: "only-admins", - SetPermissionSendMessages: "only-admins", - Expiration: 86400, - } - var response *UpdateGroupResponse - err = client.CallFor(ctx, &response, "updateGroup", ¶ms) - if err != nil { - return err - } } - g, err := getGroup(config, ts.GroupID) + if ts.GroupID == "" { + return errors.New("unable to create or update group") + } + + g, err := gc.getGroup(ts.GroupID) if err != nil { return err } - if g == nil { - return errors.New("SignalGroup Bridge: Group not found") - } if g.GroupInviteLink == "" { - return errors.New("SignalGroup Bridge: No invite link in group response") + return errors.New("unable to get group invite link") } ts.GroupInviteLink = g.GroupInviteLink @@ -148,12 +83,13 @@ func CreateOrUpdateGroup(ts *storage.TickerSignalGroup, config config.Config) er return nil } -func QuitGroup(config config.Config, groupID string) error { - ctx := context.Background() - client := rpcClient(config) - - params := QuitGroupParams{ - Account: config.SignalGroup.Account, +func (gc *GroupClient) QuitGroup(groupID string) error { + params := struct { + Account string `json:"account"` + GroupID string `json:"group-id"` + Delete bool `json:"delete"` + }{ + Account: gc.cfg.SignalGroup.Account, GroupID: groupID, Delete: true, } @@ -161,7 +97,7 @@ func QuitGroup(config config.Config, groupID string) error { // TODO: cannot leave group if I'm the last admin // Maybe promote first other member to admin? var response interface{} - err := client.CallFor(ctx, &response, "leaveGroup", ¶ms) + err := gc.client.CallFor(context.Background(), &response, "leaveGroup", ¶ms) if err != nil { return err } @@ -169,16 +105,17 @@ func QuitGroup(config config.Config, groupID string) error { return nil } -func listGroups(config config.Config) ([]*ListGroupsResponseGroup, error) { +func (gc *GroupClient) listGroups() ([]ListGroupsResponseGroup, error) { ctx := context.Background() - client := rpcClient(config) - params := ListGroupsParams{ - Account: config.SignalGroup.Account, + params := struct { + Account string `json:"account"` + }{ + Account: gc.cfg.SignalGroup.Account, } - var response []*ListGroupsResponseGroup - err := client.CallFor(ctx, &response, "listGroups", ¶ms) + var response []ListGroupsResponseGroup + err := gc.client.CallFor(ctx, &response, "listGroups", ¶ms) if err != nil { return nil, err } @@ -186,10 +123,10 @@ func listGroups(config config.Config) ([]*ListGroupsResponseGroup, error) { return response, nil } -func getGroup(config config.Config, groupID string) (*ListGroupsResponseGroup, error) { - gl, err := listGroups(config) +func (gc *GroupClient) getGroup(groupID string) (ListGroupsResponseGroup, error) { + gl, err := gc.listGroups() if err != nil { - return nil, err + return ListGroupsResponseGroup{}, err } for _, g := range gl { @@ -198,84 +135,9 @@ func getGroup(config config.Config, groupID string) (*ListGroupsResponseGroup, e } } - return nil, nil -} - -func SendGroupMessage(config config.Config, ss storage.Storage, groupID string, message *storage.Message) error { - ctx := context.Background() - client := rpcClient(config) - - var attachments []string - if len(message.Attachments) > 0 { - for _, attachment := range message.Attachments { - upload, err := ss.FindUploadByUUID(attachment.UUID) - if err != nil { - log.WithError(err).Error("failed to find upload") - continue - } - - fileContent, err := os.ReadFile(upload.FullPath(config.Upload.Path)) - if err != nil { - log.WithError(err).Error("failed to read file") - continue - } - fileBase64 := base64.StdEncoding.EncodeToString(fileContent) - aString := fmt.Sprintf("data:%s;filename=%s;base64,%s", upload.ContentType, upload.FileName, fileBase64) - attachments = append(attachments, aString) - } - } - - params := SendParams{ - Account: config.SignalGroup.Account, - GroupID: groupID, - Message: message.Text, - Attachment: attachments, - } - - var response *SendResponse - err := client.CallFor(ctx, &response, "send", ¶ms) - if err != nil { - return err - } - if response.Timestamp == nil { - return errors.New("SignalGroup Bridge: No timestamp in send response") - } - - message.SignalGroup = storage.SignalGroupMeta{ - Timestamp: response.Timestamp, - } - - return nil -} - -func DeleteMessage(config config.Config, groupID string, message *storage.Message) error { - ctx := context.Background() - client := rpcClient(config) - - params := DeleteParams{ - Account: config.SignalGroup.Account, - GroupID: groupID, - TargetTimestamp: message.SignalGroup.Timestamp, - } - - var response *SendResponse - err := client.CallFor(ctx, &response, "remoteDelete", ¶ms) - if err != nil { - return err - } - - return nil + return ListGroupsResponseGroup{}, nil } -func rpcClient(config config.Config) jsonrpc.RPCClient { - if config.SignalGroup.ApiUser != "" && config.SignalGroup.ApiPass != "" { - return jsonrpc.NewClientWithOpts(config.SignalGroup.ApiUrl, &jsonrpc.RPCClientOpts{ - CustomHeaders: map[string]string{ - "Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte(config.SignalGroup.ApiUser+":"+config.SignalGroup.ApiPass)), - }, - }) - } else { - return jsonrpc.NewClient(config.SignalGroup.ApiUrl) - - } +func Client(config config.Config) jsonrpc.RPCClient { + return jsonrpc.NewClient(config.SignalGroup.ApiUrl) } diff --git a/internal/storage/message.go b/internal/storage/message.go index 07a46f48..488eeeec 100644 --- a/internal/storage/message.go +++ b/internal/storage/message.go @@ -66,7 +66,7 @@ type BlueskyMeta struct { } type SignalGroupMeta struct { - Timestamp *int + Timestamp int } type Attachment struct {