diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 69a3607..60533b8 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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 @@ -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: @@ -807,7 +853,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SendResponse' + $ref: '#/components/schemas/GenericResponse' '400': description: Bad Request content: @@ -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: 1203632782168851111@g.us + 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: '6289987391723@s.whatsapp.net' + status: + type: string + example: success + message: + type: string + example: Participant added + UserGroupResponse: type: object properties: diff --git a/readme.md b/readme.md index 1221beb..c51df7f 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/src/config/settings.go b/src/config/settings.go index 0538c16..6e40818 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v4.12.0" + AppVersion = "v4.13.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/domains/group/group.go b/src/domains/group/group.go index 689cce9..c89e4ed 100644 --- a/src/domains/group/group.go +++ b/src/domains/group/group.go @@ -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 { @@ -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"` +} diff --git a/src/internal/rest/group.go b/src/internal/rest/group.go index 9edb2d7..95ccef6 100644 --- a/src/internal/rest/group.go +++ b/src/internal/rest/group.go @@ -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" ) @@ -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 } @@ -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) @@ -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) + + 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, + }) +} diff --git a/src/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index 139dc0b..97d0a66 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -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) diff --git a/src/services/group.go b/src/services/group.go index c386639..3e096cb 100644 --- a/src/services/group.go +++ b/src/services/group.go @@ -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 } groupConfig := whatsmeow.ReqCreateGroup{ @@ -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 +} diff --git a/src/validations/group_validation.go b/src/validations/group_validation.go index 69742e5..0758c1f 100644 --- a/src/validations/group_validation.go +++ b/src/validations/group_validation.go @@ -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 +} diff --git a/src/views/components/GroupAddParticipants.js b/src/views/components/GroupAddParticipants.js new file mode 100644 index 0000000..4d272fc --- /dev/null +++ b/src/views/components/GroupAddParticipants.js @@ -0,0 +1,119 @@ +export default { + name: 'AddParticipantsToGroup', + data() { + return { + loading: false, + group: '', + participants: ['', ''], + } + }, + computed: { + group_id() { + return `${this.group}@${window.TYPEGROUP}` + } + }, + methods: { + openModal() { + $('#modalGroupAddParticipant').modal({ + onApprove: function () { + return false; + } + }).modal('show'); + }, + handleAddParticipant() { + this.participants.push('') + }, + handleDeleteParticipant(index) { + this.participants.splice(index, 1) + }, + async handleSubmit() { + try { + let response = await this.submitApi() + showSuccessInfo(response) + $('#modalGroupAddParticipant').modal('hide'); + } catch (err) { + showErrorInfo(err) + } + }, + async submitApi() { + this.loading = true; + try { + let response = await window.http.post(`/group/participants`, { + group_id: this.group_id, + // convert participant become list of string + participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`) + }) + this.handleReset(); + return response.data.message; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } finally { + this.loading = false; + } + }, + handleReset() { + this.group = ''; + this.participants = ['', '']; + }, + }, + template: ` +