Browse Source

feat: manage participant (#169)

* feat: manage participant

* refactor(group.go): change HTTP methods for managing group participants to use POST instead of DELETE and PATCH for better compatibility and consistency

* chore: update image homepage
pull/181/head v4.15.0
Aldino Kemal 2 years ago
committed by GitHub
parent
commit
d62ef31791
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 128
      docs/openapi.yaml
  2. 9
      readme.md
  3. 2
      src/config/settings.go
  4. 12
      src/domains/group/group.go
  5. 68
      src/internal/rest/group.go
  6. 11
      src/pkg/whatsapp/whatsapp.go
  7. 6
      src/services/group.go
  8. 69
      src/views/components/GroupManageParticipants.js
  9. 2
      src/views/index.html

128
docs/openapi.yaml

@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: WhatsApp API MultiDevice
version: 4.0.0
version: 4.1.0
description: This API is used for sending whatsapp via API
servers:
- url: http://localhost:3000
@ -838,30 +838,104 @@ paths:
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'
$ref: '#/components/schemas/ManageParticipantRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantResponse'
'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/remove:
post:
operationId: removeParticipantFromGroup
tags:
- group
summary: Remove participants from group
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantResponse'
'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/promote:
post:
operationId: promoteParticipantToAdmin
tags:
- group
summary: Promote participants to admin
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AddParticipantToGroupResponse'
$ref: '#/components/schemas/ManageParticipantResponse'
'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/demote:
post:
operationId: demoteParticipantToMember
tags:
- group
summary: Demote participants to member
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ManageParticipantResponse'
'400':
description: Bad Request
content:
@ -960,7 +1034,21 @@ components:
group_id:
type: string
example: 1203632782168851111@g.us
AddParticipantToGroupResponse:
ManageParticipantRequest:
type: object
properties:
group_id:
type: string
example: 1203632782168851111@g.us
participants:
type: array
items:
type: string
example:
- '6819241294719274'
- '6829241294719274'
- '6839241294719274'
ManageParticipantResponse:
type: object
properties:
code:

9
readme.md

@ -121,13 +121,14 @@ You can fork or edit this source code !
| ✅ | React Message | POST | /message/:message_id/reaction |
| ✅ | Delete Message | POST | /message/:message_id/delete |
| ✅ | Edit Message | POST | /message/:message_id/update |
| ❌ | Star message | POST | /message/:message_id/star |
| ✅ | 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 |
| | Remove Participant in Group | DELETE | /group/participants |
| ✅ | Promote Participant in Group | PATCH | /group/participants/promote |
| ✅ | Demote Participant in Group | PATCH | /group/participants/demote |
```
✅ = Available
@ -136,7 +137,7 @@ You can fork or edit this source code !
### App User Interface
1. Homepage ![Homepage](https://i.ibb.co.com/681JTHK/image.png)
1. Homepage ![Homepage](https://i.ibb.co.com/d05L4VX/homepage.png)
2. Login ![Login](https://i.ibb.co.com/jkcB15R/login.png?v=1)
3. Send Message ![Send Message](https://i.ibb.co.com/rc3NXMX/send-message.png?v1)
4. Send Image ![Send Image](https://i.ibb.co.com/BcFL3SD/send-image.png?v1)

2
src/config/settings.go

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

12
src/domains/group/group.go

@ -1,12 +1,15 @@
package group
import "context"
import (
"context"
"go.mau.fi/whatsmeow"
)
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)
ManageParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error)
}
type JoinGroupWithLinkRequest struct {
@ -23,8 +26,9 @@ type CreateGroupRequest struct {
}
type ParticipantRequest struct {
GroupID string `json:"group_id" form:"group_id"`
Participants []string `json:"participants" form:"participants"`
GroupID string `json:"group_id" form:"group_id"`
Participants []string `json:"participants" form:"participants"`
Action whatsmeow.ParticipantChange `json:"action" form:"action"`
}
type ParticipantStatus struct {

68
src/internal/rest/group.go

@ -6,6 +6,7 @@ import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
"github.com/gofiber/fiber/v2"
"go.mau.fi/whatsmeow"
)
type Group struct {
@ -18,6 +19,9 @@ func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group {
app.Post("/group/join-with-link", rest.JoinGroupWithLink)
app.Post("/group/leave", rest.LeaveGroup)
app.Post("/group/participants", rest.AddParticipants)
app.Post("/group/participants/remove", rest.DeleteParticipants)
app.Post("/group/participants/promote", rest.PromoteParticipants)
app.Post("/group/participants/demote", rest.DemoteParticipants)
return rest
}
@ -81,7 +85,9 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error {
whatsapp.SanitizePhone(&request.GroupID)
result, err := controller.Service.AddParticipant(c.UserContext(), request)
request.Action = whatsmeow.ParticipantChangeAdd
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
@ -91,3 +97,63 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error {
Results: result,
})
}
func (controller *Group) DeleteParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID)
request.Action = whatsmeow.ParticipantChangeRemove
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success delete participants",
Results: result,
})
}
func (controller *Group) PromoteParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID)
request.Action = whatsmeow.ParticipantChangePromote
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success promote participants",
Results: result,
})
}
func (controller *Group) DemoteParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID)
request.Action = whatsmeow.ParticipantChangeDemote
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success demote participants",
Results: result,
})
}

11
src/pkg/whatsapp/whatsapp.go

@ -458,11 +458,14 @@ func ExtractMedia(storageLocation string, mediaFile whatsmeow.DownloadableMessag
extractedMedia.Caption = media.GetCaption()
}
extensions, err := mime.ExtensionsByType(extractedMedia.MimeType)
if err != nil {
return extractedMedia, err
var extension string
if ext, err := mime.ExtensionsByType(extractedMedia.MimeType); err != nil && len(ext) > 0 {
extension = ext[0]
} else if parts := strings.Split(extractedMedia.MimeType, "/"); len(parts) > 1 {
extension = "." + parts[len(parts)-1]
}
extractedMedia.MediaPath = fmt.Sprintf("%s/%d-%s%s", storageLocation, time.Now().Unix(), uuid.NewString(), extensions[0])
extractedMedia.MediaPath = fmt.Sprintf("%s/%d-%s%s", storageLocation, time.Now().Unix(), uuid.NewString(), extension)
err = os.WriteFile(extractedMedia.MediaPath, data, 0600)
if err != nil {
return extractedMedia, err

6
src/services/group.go

@ -73,7 +73,7 @@ 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) {
func (service groupService) ManageParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) {
if err = validations.ValidateParticipant(ctx, request); err != nil {
return result, err
}
@ -89,7 +89,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr
return result, err
}
participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, whatsmeow.ParticipantChangeAdd)
participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, request.Action)
if err != nil {
return result, err
}
@ -105,7 +105,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr
result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(),
Status: "success",
Message: "Participant added",
Message: "Action success",
})
}
}

69
src/views/components/GroupAddParticipants.js → src/views/components/GroupManageParticipants.js

@ -1,48 +1,66 @@
export default {
name: 'AddParticipantsToGroup',
name: 'ManageGroupParticipants',
data() {
return {
loading: false,
group: '',
action: 'add', // add, remove, promote, demote
participants: ['', ''],
}
};
},
computed: {
group_id() {
return `${this.group}@${window.TYPEGROUP}`
}
return `${this.group}@${window.TYPEGROUP}`;
},
},
methods: {
openModal() {
$('#modalGroupAddParticipant').modal({
onApprove: function () {
onApprove: function() {
return false;
}
},
}).modal('show');
},
handleAddParticipant() {
this.participants.push('')
this.participants.push('');
},
handleDeleteParticipant(index) {
this.participants.splice(index, 1)
this.participants.splice(index, 1);
},
async handleSubmit() {
try {
let response = await this.submitApi()
showSuccessInfo(response)
let response = await this.submitApi();
showSuccessInfo(response);
$('#modalGroupAddParticipant').modal('hide');
} catch (err) {
showErrorInfo(err)
showErrorInfo(err);
}
},
async submitApi() {
this.loading = true;
try {
let response = await window.http.post(`/group/participants`, {
const payload = {
group_id: this.group_id,
// convert participant become list of string
participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`)
})
participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`),
};
let response;
switch (this.action) {
case 'add':
response = await window.http.post(`/group/participants`, payload);
break;
case 'remove':
response = await window.http.post(`/group/participants/remove`, payload);
break;
case 'promote':
response = await window.http.post(`/group/participants/promote`, payload);
break;
case 'demote':
response = await window.http.post(`/group/participants/demote`, payload);
break;
}
this.handleReset();
return response.data.message;
} catch (error) {
@ -56,6 +74,7 @@ export default {
},
handleReset() {
this.group = '';
this.action = 'add';
this.participants = ['', ''];
},
},
@ -63,9 +82,9 @@ export default {
<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="header">Manage Participants</div>
<div class="description">
Add multiple participants
Add/Remove/Promote/Demote Participants
</div>
</div>
</div>
@ -74,7 +93,7 @@ export default {
<div class="ui small modal" id="modalGroupAddParticipant">
<i class="close icon"></i>
<div class="header">
Add Participants
Manage Participants
</div>
<div class="content">
<form class="ui form">
@ -105,15 +124,25 @@ export default {
</div>
</div>
</div>
<div class="field">
<label>Action</label>
<select v-model="action" class="ui dropdown" aria-label="Action">
<option value="add">Add to group</option>
<option value="remove">Remove from group</option>
<option value="promote">Promote to admin</option>
<option value="demote">Demote from admin</option>
</select>
</div>
</form>
</div>
<div class="actions">
<div class="ui approve positive right labeled icon button" :class="{'loading': this.loading}"
@click="handleSubmit" type="button">
Create
Submit
<i class="send icon"></i>
</div>
</div>
</div>
`
}
`,
};

2
src/views/index.html

@ -144,7 +144,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 GroupAddParticipants from "./components/GroupManageParticipants.js";
import AccountAvatar from "./components/AccountAvatar.js";
import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js";

Loading…
Cancel
Save