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: add group participants #132

Merged
merged 7 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
90 changes: 87 additions & 3 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: WhatsApp API MultiDevice
version: 3.10.1
version: 3.11.0
description: This API is used for sending whatsapp via API
servers:
- url: http://localhost:3000
Expand Down Expand Up @@ -773,7 +773,53 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/SendResponse'
$ref: '#/components/schemas/CreateGroupResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBadRequest'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorInternalServer'
/group/participants:
post:
operationId: addParticipantToGroup
tags:
- group
summary: Adding more participants to group
requestBody:
content:
application/json:
schema:
type: object
properties:
group_id:
type: string
example: '120363228882361111'
participants:
type: array
items:
type: string
example:
- '6819241294719274'
- '6829241294719274'
- '6839241294719274'
example:
- '6819241294719274'
- '6829241294719274'
- '6839241294719274'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AddParticipantToGroupResponse'
'400':
description: Bad Request
content:
Expand Down Expand Up @@ -807,7 +853,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/SendResponse'
$ref: '#/components/schemas/GenericResponse'
'400':
description: Bad Request
content:
Expand Down Expand Up @@ -857,6 +903,44 @@ paths:

components:
schemas:
CreateGroupResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success get list groups
results:
type: object
properties:
group_id:
type: string
example: [email protected]
AddParticipantToGroupResponse:
type: object
properties:
code:
type: string
example: SUCCESS
message:
type: string
example: Success get list groups
results:
type: array
items:
properties:
participant:
type: string
example: '[email protected]'
status:
type: string
example: success
message:
type: string
example: Participant added

UserGroupResponse:
type: object
properties:
Expand Down
61 changes: 31 additions & 30 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,36 +96,37 @@ You can fork or edit this source code !

### Current API

You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API, furthermore you can generate HTTP Client from this
API using [openapi-generator](https://openapi-generator.tech/#try)

| Feature | Menu | Method | URL |
|---------|--------------------------------|--------|-----------------------------|
| ✅ | Login | GET | /app/login |
| ✅ | Logout | GET | /app/logout |
| ✅ | Reconnect | GET | /app/reconnect |
| ✅ | User Info | GET | /user/info |
| ✅ | User Avatar | GET | /user/avatar |
| ✅ | User My Group List | GET | /user/my/groups |
| ✅ | User My Privacy Setting | GET | /user/my/privacy |
| ✅ | Send Message | POST | /send/message |
| ✅ | Send Image | POST | /send/image |
| ✅ | Send Audio | POST | /send/audio |
| ✅ | Send File | POST | /send/file |
| ✅ | Send Video | POST | /send/video |
| ✅ | Send Contact | POST | /send/contact |
| ✅ | Send Link | POST | /send/link |
| ✅ | Send Location | POST | /send/location |
| ✅ | Send Poll / Vote | POST | /send/poll |
| ✅ | Revoke Message | POST | /message/:message_id/revoke |
| ✅ | React Message | POST | /message/:message_id/react |
| ✅ | Edit Message | POST | /message/:message_id/update |
| ✅ | Join Group With Link | POST | /group/join-with-link |
| ✅ | Leave Group | POST | /group/leave |
| ✅ | Create Group | POST | /group |
| ❌ | Add More Participants in Group | POST | |
| ❌ | Remove Participant in Group | POST | |
| ❌ | Promote Participant in Group | POST | |
- You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API or paste to [SwaggerEditor](https://editor.swagger.io).
- Furthermore you can generate HTTP Client from this API using [openapi-generator](https://openapi-generator.tech/#try)

| Feature | Menu | Method | URL |
|---------|------------------------------|--------|-----------------------------|
| ✅ | Login | GET | /app/login |
| ✅ | Logout | GET | /app/logout |
| ✅ | Reconnect | GET | /app/reconnect |
| ✅ | User Info | GET | /user/info |
| ✅ | User Avatar | GET | /user/avatar |
| ✅ | User My Group List | GET | /user/my/groups |
| ✅ | User My Privacy Setting | GET | /user/my/privacy |
| ✅ | Send Message | POST | /send/message |
| ✅ | Send Image | POST | /send/image |
| ✅ | Send Audio | POST | /send/audio |
| ✅ | Send File | POST | /send/file |
| ✅ | Send Video | POST | /send/video |
| ✅ | Send Contact | POST | /send/contact |
| ✅ | Send Link | POST | /send/link |
| ✅ | Send Location | POST | /send/location |
| ✅ | Send Poll / Vote | POST | /send/poll |
| ✅ | Revoke Message | POST | /message/:message_id/revoke |
| ✅ | React Message | POST | /message/:message_id/react |
| ✅ | Edit Message | POST | /message/:message_id/update |
| ✅ | Join Group With Link | POST | /group/join-with-link |
| ✅ | Leave Group | POST | /group/leave |
| ✅ | Create Group | POST | /group |
| ✅ | Add Participants in Group | POST | /group/participants |
| ❌ | Remove Participant in Group | DELETE | /group/participants |
| ❌ | Promote Participant in Group | POST | /group/participants/promote |
| ❌ | Demote Participant in Group | POST | /group/participants/demote |

```
✅ = Available
Expand Down
2 changes: 1 addition & 1 deletion src/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

var (
AppVersion = "v4.12.0"
AppVersion = "v4.13.0"
AppPort = "3000"
AppDebug = false
AppOs = "AldinoKemal"
Expand Down
12 changes: 12 additions & 0 deletions src/domains/group/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type IGroupService interface {
JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error)
LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error)
CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, err error)
AddParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error)
}

type JoinGroupWithLinkRequest struct {
Expand All @@ -20,3 +21,14 @@ type CreateGroupRequest struct {
Title string `json:"title" form:"title"`
Participants []string `json:"participants" form:"participants"`
}

type ParticipantRequest struct {
GroupID string `json:"group_id" form:"group_id"`
Participants []string `json:"participants" form:"participants"`
}

type ParticipantStatus struct {
Participant string `json:"participant"`
Status string `json:"status"`
Message string `json:"message"`
}
22 changes: 22 additions & 0 deletions src/internal/rest/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
"github.com/gofiber/fiber/v2"
)

Expand All @@ -16,6 +17,7 @@ func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group {
app.Post("/group", rest.CreateGroup)
app.Post("/group/join-with-link", rest.JoinGroupWithLink)
app.Post("/group/leave", rest.LeaveGroup)
app.Post("/group/participants", rest.AddParticipants)
return rest
}

Expand All @@ -42,6 +44,8 @@ func (controller *Group) LeaveGroup(c *fiber.Ctx) error {
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)

whatsapp.SanitizePhone(&request.GroupID)

err = controller.Service.LeaveGroup(c.UserContext(), request)
utils.PanicIfNeeded(err)

Expand Down Expand Up @@ -69,3 +73,21 @@ func (controller *Group) CreateGroup(c *fiber.Ctx) error {
},
})
}

func (controller *Group) AddParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)

whatsapp.SanitizePhone(&request.GroupID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanitize GroupID to prevent injection attacks.

- whatsapp.SanitizePhone(&request.GroupID)
+ sanitizedGroupID, err := whatsapp.SanitizeGroupID(request.GroupID)
+ if err != nil {
+     return c.Status(fiber.StatusBadRequest).JSON(utils.ResponseError{Message: "Invalid Group ID"})
+ }
+ request.GroupID = sanitizedGroupID

Ensure that GroupID is properly sanitized to prevent potential injection attacks. Consider implementing a dedicated sanitization function if not already available.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
whatsapp.SanitizePhone(&request.GroupID)
sanitizedGroupID, err := whatsapp.SanitizeGroupID(request.GroupID)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.ResponseError{Message: "Invalid Group ID"})
}
request.GroupID = sanitizedGroupID


result, err := controller.Service.AddParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)

return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success add participants",
Results: result,
})
}
3 changes: 0 additions & 3 deletions src/pkg/whatsapp/whatsapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,6 @@ func handler(rawEvt interface{}) {
if evt.IsViewOnce {
metaParts = append(metaParts, "view once")
}
if evt.IsViewOnce {
metaParts = append(metaParts, "ephemeral")
}

log.Infof("Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message)

Expand Down
70 changes: 59 additions & 11 deletions src/services/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,9 @@ func (service groupService) CreateGroup(ctx context.Context, request domainGroup
}
whatsapp.MustLogin(service.WaCli)

var participantsJID []types.JID
for _, participant := range request.Participants {
formattedParticipant := participant + config.WhatsappTypeUser

if !whatsapp.IsOnWhatsapp(service.WaCli, formattedParticipant) {
return "", pkgError.ErrUserNotRegistered
}

if participantJID, err := types.ParseJID(formattedParticipant); err == nil {
participantsJID = append(participantsJID, participantJID)
}
participantsJID, err := service.participantToJID(request.Participants)
if err != nil {
return
Comment on lines +56 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle errors explicitly in participantToJID calls.

- participantsJID, err := service.participantToJID(request.Participants)
- if err != nil {
-     return
- }
+ participantsJID, err := service.participantToJID(request.Participants)
+ if err != nil {
+     return nil, err
+ }

Ensure that errors returned by participantToJID are handled explicitly to avoid silent failures and improve error reporting.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
participantsJID, err := service.participantToJID(request.Participants)
if err != nil {
return
participantsJID, err := service.participantToJID(request.Participants)
if err != nil {
return nil, err

}

groupConfig := whatsmeow.ReqCreateGroup{
Expand All @@ -80,3 +72,59 @@ func (service groupService) CreateGroup(ctx context.Context, request domainGroup

return groupInfo.JID.String(), nil
}

func (service groupService) AddParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) {
if err = validations.ValidateParticipant(ctx, request); err != nil {
return result, err
}
whatsapp.MustLogin(service.WaCli)

groupJID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.GroupID)
if err != nil {
return result, err
}

participantsJID, err := service.participantToJID(request.Participants)
if err != nil {
return result, err
}

participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, whatsmeow.ParticipantChangeAdd)
if err != nil {
return result, err
}

for _, participant := range participants {
if participant.Error == 403 && participant.AddRequest != nil {
result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(),
Status: "error",
Message: "Failed to add participant",
})
} else {
result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(),
Status: "success",
Message: "Participant added",
})
}
}

return result, nil
}

func (service groupService) participantToJID(participants []string) ([]types.JID, error) {
var participantsJID []types.JID
for _, participant := range participants {
formattedParticipant := participant + config.WhatsappTypeUser

if !whatsapp.IsOnWhatsapp(service.WaCli, formattedParticipant) {
return nil, pkgError.ErrUserNotRegistered
}

if participantJID, err := types.ParseJID(formattedParticipant); err == nil {
participantsJID = append(participantsJID, participantJID)
}
}
return participantsJID, nil
}
14 changes: 14 additions & 0 deletions src/validations/group_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,17 @@ func ValidateCreateGroup(ctx context.Context, request domainGroup.CreateGroupReq

return nil
}

func ValidateParticipant(ctx context.Context, request domainGroup.ParticipantRequest) error {
err := validation.ValidateStructWithContext(ctx, &request,
validation.Field(&request.GroupID, validation.Required),
validation.Field(&request.Participants, validation.Required),
validation.Field(&request.Participants, validation.Each(validation.Required)),
)

if err != nil {
return pkgError.ValidationError(err.Error())
}

return nil
}
Loading