Browse Source

feat: add group participants (#132)

* feat: add group participant api

* fix: sanitize leave group

* chore: update openapi

* feat: ui add participant

* chore: update docs

* chore: update docs

* chore: update docs
pull/152/head v4.13.0
Aldino Kemal 2 years ago
committed by GitHub
parent
commit
e78767d289
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 90
      docs/openapi.yaml
  2. 13
      readme.md
  3. 2
      src/config/settings.go
  4. 12
      src/domains/group/group.go
  5. 22
      src/internal/rest/group.go
  6. 3
      src/pkg/whatsapp/whatsapp.go
  7. 70
      src/services/group.go
  8. 14
      src/validations/group_validation.go
  9. 119
      src/views/components/GroupAddParticipants.js
  10. 4
      src/views/index.html

90
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:

13
readme.md

@ -93,11 +93,11 @@ 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)
- 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 |
@ -120,9 +120,10 @@ API using [openapi-generator](https://openapi-generator.tech/#try)
| ✅ | 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 | |
| ✅ | 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

2
src/config/settings.go

@ -5,7 +5,7 @@ import (
)
var (
AppVersion = "v4.12.0"
AppVersion = "v4.13.0"
AppPort = "3000"
AppDebug = false
AppOs = "AldinoKemal"

12
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"`
}

22
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,
})
}

3
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)

70
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
}

14
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
}

119
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: `
<div class="green card" @click="openModal" style="cursor: pointer">
<div class="content">
<a class="ui green right ribbon label">Group</a>
<div class="header">Add Participants</div>
<div class="description">
Add multiple participants
</div>
</div>
</div>
<!-- Modal AccountGroup -->
<div class="ui small modal" id="modalGroupAddParticipant">
<i class="close icon"></i>
<div class="header">
Add Participants
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Group ID</label>
<input v-model="group" type="text"
placeholder="12036322888236XXXX..."
aria-label="Group Name">
<input :value="group_id" disabled aria-label="whatsapp_id">
</div>
<div class="field">
<label>Participants</label>
<div style="display: flex; flex-direction: column; gap: 5px">
<div class="ui action input" :key="index" v-for="(participant, index) in participants">
<input type="number" placeholder="Phone Int Number (6289...)" v-model="participants[index]"
aria-label="list participant">
<button class="ui button" @click="handleDeleteParticipant(index)" type="button">
<i class="minus circle icon"></i>
</button>
</div>
<div class="field" style="display: flex; flex-direction: column; gap: 3px">
<div>
<button class="mini ui primary button" @click="handleAddParticipant" type="button">
<i class="plus icon"></i> Option
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="actions">
<div class="ui approve positive right labeled icon button" :class="{'loading': this.loading}"
@click="handleSubmit" type="button">
Create
<i class="send icon"></i>
</div>
</div>
</div>
`
}

4
src/views/index.html

@ -81,6 +81,7 @@
<group-list></group-list>
<group-create></group-create>
<group-join-with-link></group-join-with-link>
<group-add-participants></group-add-participants>
</div>
<div class="ui horizontal divider">
@ -141,6 +142,7 @@
import GroupList from "./components/GroupList.js";
import GroupCreate from "./components/GroupCreate.js";
import GroupJoinWithLink from "./components/GroupJoinWithLink.js";
import GroupAddParticipants from "./components/GroupAddParticipants.js";
import AccountAvatar from "./components/AccountAvatar.js";
import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js";
@ -169,7 +171,7 @@
AppLogin, AppLogout, AppReconnect,
SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll,
MessageUpdate, MessageReact, MessageRevoke,
GroupList, GroupCreate, GroupJoinWithLink,
GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants,
AccountAvatar, AccountUserInfo, AccountPrivacy
},
delimiters: ['[[', ']]'],

Loading…
Cancel
Save