diff --git a/README.md b/README.md index 9ef2b7a..6b7d940 100644 --- a/README.md +++ b/README.md @@ -58,13 +58,14 @@ The `signal-cli-rest-api` supports three different modes of execution, which can * **`normal` Mode: (Default)** The `signal-cli` executable is invoked for every REST API request. Being a Java application, each REST call requires a new startup of the JVM (Java Virtual Machine), increasing the latency and hence leading to the slowest mode of operation. * **`native` Mode:** A precompiled binary `signal-cli-native` (using GraalVM) is used for every REST API request. This results in a much lower latency & memory usage on each call. On the `armv7` platform this mode is not available and falls back to `normal`. The native mode may also be less stable, due to the experimental state of GraalVM compiler. * `json-rpc` Mode: A single, JVM-based `signal-cli` instance is spawned as daemon process. This mode is usually the fastest, but requires more memory as the JVM keeps running. - +* `json-rpc-native` Mode: Uses the `signal-cli-native` binary and starts it in daemon mode (this mode basically combines the advantages of the `native` mode and the `json-rpc` mode). | mode | speed | resident memory usage | | ---------: | :------------------------------------------------------- | :-------------------- | | `normal` | :heavy_check_mark: | normal | | `native` | :heavy_check_mark: :heavy_check_mark: | normal | | `json-rpc` | :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: | increased | +| `json-rpc-native` | :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: | normal | **Example of running `signal-cli-rest` in `native` mode** diff --git a/entrypoint.sh b/entrypoint.sh index 05363d3..027254a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,7 +27,7 @@ cap_prefix="-cap_" caps="$cap_prefix$(seq -s ",$cap_prefix" 0 $(cat /proc/sys/kernel/cap_last_cap))" # TODO: check mode -if [ "$MODE" = "json-rpc" ] +if [ "$MODE" = "json-rpc" ] || [ "$MODE" = "json-rpc-native" ] then /usr/bin/jsonrpc2-helper if [ -n "$JAVA_OPTS" ] ; then diff --git a/src/api/api.go b/src/api/api.go index a169480..d12efdd 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -1024,7 +1024,7 @@ func (a *Api) RemoveAdminsFromGroup(c *gin.Context) { // @Success 200 {object} []client.GroupEntry // @Failure 400 {object} Error // @Param number path string true "Registered Phone Number" -// @Param use_only_uuid_as_identifier query bool false "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members" +// @Param expand query bool false "Expand the response to show more details (default: false)" // @Router /v1/groups/{number} [get] func (a *Api) GetGroups(c *gin.Context) { number, err := url.PathUnescape(c.Param("number")) @@ -1033,18 +1033,26 @@ func (a *Api) GetGroups(c *gin.Context) { return } - useOnlyUuidAsIdentifier := c.DefaultQuery("use_only_uuid_as_identifier", "false") - if useOnlyUuidAsIdentifier != "true" && useOnlyUuidAsIdentifier != "false" { - c.JSON(400, Error{Msg: "Couldn't process request - use_only_uuid_as_identifier parameter needs to be either 'true' or 'false'"}) + expand := c.DefaultQuery("expand", "false") + if expand != "true" && expand != "false" { + c.JSON(400, Error{Msg: "Couldn't process request - expand parameter needs to be either 'true' or 'false'"}) return } - groups, err := a.signalClient.GetGroups(number, StringToBool(useOnlyUuidAsIdentifier)) - if err != nil { - c.JSON(400, Error{Msg: err.Error()}) - return + var groups any + if StringToBool(expand) { + groups, err = a.signalClient.GetGroupsExpanded(number) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + } else { + groups, err = a.signalClient.GetGroups(number) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } } - c.JSON(200, groups) } @@ -1057,7 +1065,7 @@ func (a *Api) GetGroups(c *gin.Context) { // @Failure 400 {object} Error // @Param number path string true "Registered Phone Number" // @Param groupid path string true "Group ID" -// @Param use_only_uuid_as_identifier query bool false "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members" +// @Param expand query bool false "Expand the response to show more details (default: false)" // @Router /v1/groups/{number}/{groupid} [get] func (a *Api) GetGroup(c *gin.Context) { number, err := url.PathUnescape(c.Param("number")) @@ -1067,16 +1075,25 @@ func (a *Api) GetGroup(c *gin.Context) { } groupId := c.Param("groupid") - useOnlyUuidAsIdentifier := c.DefaultQuery("use_only_uuid_as_identifier", "false") - if useOnlyUuidAsIdentifier != "true" && useOnlyUuidAsIdentifier != "false" { - c.JSON(400, Error{Msg: "Couldn't process request - use_only_uuid_as_identifier parameter needs to be either 'true' or 'false'"}) + expand := c.DefaultQuery("expand", "false") + if expand != "true" && expand != "false" { + c.JSON(400, Error{Msg: "Couldn't process request - expand parameter needs to be either 'true' or 'false'"}) return } - groupEntry, err := a.signalClient.GetGroup(number, groupId, StringToBool(useOnlyUuidAsIdentifier)) - if err != nil { - c.JSON(400, Error{Msg: err.Error()}) - return + var groupEntry any + if StringToBool(expand) { + groupEntry, err = a.signalClient.GetGroupExpanded(number, groupId) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + } else { + groupEntry, err = a.signalClient.GetGroup(number, groupId) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } } if groupEntry != nil { diff --git a/src/client/client.go b/src/client/client.go index b2db5dd..5bf2fea 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -126,6 +126,30 @@ type GroupEntry struct { Permissions ds.GroupPermissions `json:"permissions"` } +type GroupMember struct { + Number string `json:"number"` + Uuid string `json:"uuid"` +} + +type GroupAdmin struct { + Number string `json:"number"` + Uuid string `json:"uuid"` +} + +type ExpandedGroupEntry struct { + Name string `json:"name"` + Description string `json:"description"` + Id string `json:"id"` + InternalId string `json:"internal_id"` + Members []GroupMember `json:"members"` + Blocked bool `json:"blocked"` + PendingInvites []GroupMember `json:"pending_invites"` + PendingRequests []GroupMember `json:"pending_requests"` + InviteLink string `json:"invite_link"` + Admins []GroupAdmin `json:"admins"` + Permissions ds.GroupPermissions `json:"permissions"` +} + type IdentityEntry struct { Number string `json:"number"` Status string `json:"status"` @@ -135,31 +159,21 @@ type IdentityEntry struct { Uuid string `json:"uuid"` } -type SignalCliGroupMember struct { - Number string `json:"number"` - Uuid string `json:"uuid"` -} - -type SignalCliGroupAdmin struct { - Number string `json:"number"` - Uuid string `json:"uuid"` -} - type SignalCliGroupEntry struct { - Name string `json:"name"` - Description string `json:"description"` - Id string `json:"id"` - IsMember bool `json:"isMember"` - IsBlocked bool `json:"isBlocked"` - Members []SignalCliGroupMember `json:"members"` - PendingMembers []SignalCliGroupMember `json:"pendingMembers"` - RequestingMembers []SignalCliGroupMember `json:"requestingMembers"` - GroupInviteLink string `json:"groupInviteLink"` - Admins []SignalCliGroupAdmin `json:"admins"` - Uuid string `json:"uuid"` - PermissionEditDetails string `json:"permissionEditDetails"` - PermissionAddMember string `json:"permissionAddMember"` - PermissionSendMessage string `json:"permissionSendMessage"` + Name string `json:"name"` + Description string `json:"description"` + Id string `json:"id"` + IsMember bool `json:"isMember"` + IsBlocked bool `json:"isBlocked"` + Members []GroupMember `json:"members"` + PendingMembers []GroupMember `json:"pendingMembers"` + RequestingMembers []GroupMember `json:"requestingMembers"` + GroupInviteLink string `json:"groupInviteLink"` + Admins []GroupAdmin `json:"admins"` + Uuid string `json:"uuid"` + PermissionEditDetails string `json:"permissionEditDetails"` + PermissionAddMember string `json:"permissionAddMember"` + PermissionSendMessage string `json:"permissionSendMessage"` } type SignalCliIdentityEntry struct { @@ -341,18 +355,6 @@ func getSignalCliModeString(signalCliMode SignalCliMode) string { return "unknown" } -func pickGroupMemberIdentifier(number string, uuid string, useOnlyUuidAsIdentifier bool) string { - if useOnlyUuidAsIdentifier { - return uuid - } - - if number != "" { - return number - } - - return uuid -} - func getRecipientType(s string) (ds.RecpType, error) { // check if the provided recipient is of type 'group' if strings.HasPrefix(s, groupPrefix) { // if the recipient starts with 'group.' it is either a group or a username that starts with 'group.' @@ -1193,7 +1195,7 @@ func (s *SignalClient) updateGroupMembers(number string, groupId string, members return nil } - group, err := s.GetGroup(number, groupId, false) + group, err := s.GetGroup(number, groupId) if err != nil { return err } @@ -1255,7 +1257,7 @@ func (s *SignalClient) updateGroupAdmins(number string, groupId string, admins [ return nil } - group, err := s.GetGroup(number, groupId, false) + group, err := s.GetGroup(number, groupId) if err != nil { return err } @@ -1312,8 +1314,8 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi return s.updateGroupAdmins(number, groupId, admins, false) } -func (s *SignalClient) GetGroups(number string, useOnlyUuidAsIdentifier bool) ([]GroupEntry, error) { - groupEntries := []GroupEntry{} +func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) { + groupEntries := []ExpandedGroupEntry{} var signalCliGroupEntries []SignalCliGroupEntry var err error @@ -1341,7 +1343,7 @@ func (s *SignalClient) GetGroups(number string, useOnlyUuidAsIdentifier bool) ([ } for _, signalCliGroupEntry := range signalCliGroupEntries { - var groupEntry GroupEntry + var groupEntry ExpandedGroupEntry groupEntry.InternalId = signalCliGroupEntry.Id groupEntry.Name = signalCliGroupEntry.Name groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id) @@ -1350,35 +1352,10 @@ func (s *SignalClient) GetGroups(number string, useOnlyUuidAsIdentifier bool) ([ groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage) groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage) groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember) - - members := []string{} - for _, val := range signalCliGroupEntry.Members { - identifier := pickGroupMemberIdentifier(val.Number, val.Uuid, useOnlyUuidAsIdentifier) - members = append(members, identifier) - } - groupEntry.Members = members - - pendingMembers := []string{} - for _, val := range signalCliGroupEntry.PendingMembers { - identifier := pickGroupMemberIdentifier(val.Number, val.Uuid, useOnlyUuidAsIdentifier) - pendingMembers = append(pendingMembers, identifier) - } - groupEntry.PendingInvites = pendingMembers - - requestingMembers := []string{} - for _, val := range signalCliGroupEntry.RequestingMembers { - identifier := pickGroupMemberIdentifier(val.Number, val.Uuid, useOnlyUuidAsIdentifier) - requestingMembers = append(requestingMembers, identifier) - } - groupEntry.PendingRequests = requestingMembers - - admins := []string{} - for _, val := range signalCliGroupEntry.Admins { - identifier := pickGroupMemberIdentifier(val.Number, val.Uuid, useOnlyUuidAsIdentifier) - admins = append(admins, identifier) - } - groupEntry.Admins = admins - + groupEntry.Members = signalCliGroupEntry.Members + groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers + groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers + groupEntry.Admins = signalCliGroupEntry.Admins groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink groupEntries = append(groupEntries, groupEntry) @@ -1387,9 +1364,84 @@ func (s *SignalClient) GetGroups(number string, useOnlyUuidAsIdentifier bool) ([ return groupEntries, nil } -func (s *SignalClient) GetGroup(number string, groupId string, useOnlyUuidAsIdentifier bool) (*GroupEntry, error) { +func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) { + expandedGroupEntries, err := s.GetGroupsExpanded(number) + if err != nil { + return []GroupEntry{}, err + } + + groupEntries := []GroupEntry{} + for _, expandedGroupEntry := range expandedGroupEntries { + groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name, + Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description, + Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink} + + members := []string{} + for _, val := range expandedGroupEntry.Members { + identifier := val.Number + if identifier == "" { + identifier = val.Uuid + } + members = append(members, identifier) + } + groupEntry.Members = members + + pendingInvites := []string{} + for _, val := range expandedGroupEntry.PendingInvites { + identifier := val.Number + if identifier == "" { + identifier = val.Uuid + } + pendingInvites = append(pendingInvites, identifier) + } + groupEntry.PendingInvites = pendingInvites + + pendingRequests := []string{} + for _, val := range expandedGroupEntry.PendingRequests { + identifier := val.Number + if identifier == "" { + identifier = val.Uuid + } + pendingRequests = append(pendingRequests, identifier) + } + groupEntry.PendingRequests = pendingRequests + + admins := []string{} + for _, val := range expandedGroupEntry.Admins { + identifier := val.Number + if identifier == "" { + identifier = val.Uuid + } + admins = append(admins, identifier) + } + groupEntry.Admins = admins + + groupEntries = append(groupEntries, groupEntry) + } + + return groupEntries, nil +} + +func (s *SignalClient) GetGroup(number string, groupId string) (*GroupEntry, error) { groupEntry := GroupEntry{} - groups, err := s.GetGroups(number, useOnlyUuidAsIdentifier) + groups, err := s.GetGroups(number) + if err != nil { + return nil, err + } + + for _, group := range groups { + if group.Id == groupId { + groupEntry = group + return &groupEntry, nil + } + } + + return nil, nil +} + +func (s *SignalClient) GetGroupExpanded(number string, groupId string) (*ExpandedGroupEntry, error) { + groupEntry := ExpandedGroupEntry{} + groups, err := s.GetGroupsExpanded(number) if err != nil { return nil, err } diff --git a/src/docs/docs.go b/src/docs/docs.go index 23dbbb6..e46b0dd 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -920,8 +920,8 @@ const docTemplate = `{ }, { "type": "boolean", - "description": "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members", - "name": "use_only_uuid_as_identifier", + "description": "Expand the response to show more details (default: false)", + "name": "expand", "in": "query" } ], @@ -1019,8 +1019,8 @@ const docTemplate = `{ }, { "type": "boolean", - "description": "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members", - "name": "use_only_uuid_as_identifier", + "description": "Expand the response to show more details (default: false)", + "name": "expand", "in": "query" } ], diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 241a2e4..8ab740d 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -917,8 +917,8 @@ }, { "type": "boolean", - "description": "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members", - "name": "use_only_uuid_as_identifier", + "description": "Expand the response to show more details (default: false)", + "name": "expand", "in": "query" } ], @@ -1016,8 +1016,8 @@ }, { "type": "boolean", - "description": "Use UUIDs instead of phone numbers as identifier for (pending|requesting) members", - "name": "use_only_uuid_as_identifier", + "description": "Expand the response to show more details (default: false)", + "name": "expand", "in": "query" } ], diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index b2e2394..b189493 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -1163,10 +1163,9 @@ paths: name: number required: true type: string - - description: Use UUIDs instead of phone numbers as identifier for (pending|requesting) - members + - description: 'Expand the response to show more details (default: false)' in: query - name: use_only_uuid_as_identifier + name: expand type: boolean produces: - application/json @@ -1259,10 +1258,9 @@ paths: name: groupid required: true type: string - - description: Use UUIDs instead of phone numbers as identifier for (pending|requesting) - members + - description: 'Expand the response to show more details (default: false)' in: query - name: use_only_uuid_as_identifier + name: expand type: boolean produces: - application/json diff --git a/src/main.go b/src/main.go index 6c271ce..b71ab2d 100644 --- a/src/main.go +++ b/src/main.go @@ -123,7 +123,7 @@ func main() { mode := utils.GetEnv("MODE", "normal") if mode == "normal" { signalCliMode = client.Normal - } else if mode == "json-rpc" { + } else if mode == "json-rpc" || mode == "json-rpc-native" { signalCliMode = client.JsonRpc } else if mode == "native" { signalCliMode = client.Native diff --git a/src/scripts/jsonrpc2-helper.go b/src/scripts/jsonrpc2-helper.go index 181dd02..ee4a433 100644 --- a/src/scripts/jsonrpc2-helper.go +++ b/src/scripts/jsonrpc2-helper.go @@ -27,6 +27,12 @@ func main() { args := []string{"--output=json", "--config", signalCliConfigDir} + signalCliBinary := "signal-cli" + signalMode := utils.GetEnv("MODE", "json-rpc") + if signalMode == "json-rpc-native" { + signalCliBinary = "signal-cli-native" + } + trustNewIdentitiesEnv := utils.GetEnv("JSON_RPC_TRUST_NEW_IDENTITIES", "") if trustNewIdentitiesEnv == "on-first-use" { args = append(args, []string{"--trust-new-identities", "on-first-use"}...) @@ -40,11 +46,6 @@ func main() { args = append(args, "daemon") - ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "") - if ignoreAttachments == "true" { - args = append(args, "--ignore-attachments") - } - ignoreStories := utils.GetEnv("JSON_RPC_IGNORE_STORIES", "") if ignoreStories == "true" { args = append(args, "--ignore-stories") @@ -60,6 +61,11 @@ func main() { args = append(args, "--ignore-stickers") } + ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "") + if ignoreAttachments == "true" { + args = append(args, "--ignore-attachments") + } + args = append(args, []string{"--tcp", "127.0.0.1:" + strconv.FormatInt(tcpPort, 10)}...) // write jsonrpc.yml config file @@ -72,7 +78,7 @@ func main() { env := os.Environ() - err = syscall.Exec("/usr/bin/signal-cli", args, env) + err = syscall.Exec("/usr/bin/"+signalCliBinary, args, env) if err != nil { log.Fatal("Couldn't start signal-cli in json-rpc mode: ", err.Error()) }