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. 8
      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 openapi: 3.0.0
info: info:
title: WhatsApp API MultiDevice title: WhatsApp API MultiDevice
version: 4.0.0
version: 4.1.0
description: This API is used for sending whatsapp via API description: This API is used for sending whatsapp via API
servers: servers:
- url: http://localhost:3000 - url: http://localhost:3000
@ -838,30 +838,104 @@ paths:
content: content:
application/json: application/json:
schema: 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/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: responses:
'200': '200':
description: OK description: OK
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/AddParticipantToGroupResponse'
$ref: '#/components/schemas/ManageParticipantResponse'
'400': '400':
description: Bad Request description: Bad Request
content: content:
@ -960,7 +1034,21 @@ components:
group_id: group_id:
type: string type: string
example: 1203632782168851111@g.us 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 type: object
properties: properties:
code: code:

9
readme.md

@ -121,13 +121,14 @@ You can fork or edit this source code !
| ✅ | React Message | POST | /message/:message_id/reaction | | ✅ | React Message | POST | /message/:message_id/reaction |
| ✅ | Delete Message | POST | /message/:message_id/delete | | ✅ | Delete Message | POST | /message/:message_id/delete |
| ✅ | Edit Message | POST | /message/:message_id/update | | ✅ | Edit Message | POST | /message/:message_id/update |
| ❌ | Star message | POST | /message/:message_id/star |
| ✅ | Join Group With Link | POST | /group/join-with-link | | ✅ | Join Group With Link | POST | /group/join-with-link |
| ✅ | Leave Group | POST | /group/leave | | ✅ | Leave Group | POST | /group/leave |
| ✅ | Create Group | POST | /group | | ✅ | Create Group | POST | /group |
| ✅ | Add Participants in Group | POST | /group/participants | | ✅ | 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 ✅ = Available
@ -136,7 +137,7 @@ You can fork or edit this source code !
### App User Interface ### 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) 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) 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) 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 ( var (
AppVersion = "v4.14.2"
AppVersion = "v4.15.0"
AppPort = "3000" AppPort = "3000"
AppDebug = false AppDebug = false
AppOs = "AldinoKemal" AppOs = "AldinoKemal"

8
src/domains/group/group.go

@ -1,12 +1,15 @@
package group package group
import "context"
import (
"context"
"go.mau.fi/whatsmeow"
)
type IGroupService interface { type IGroupService interface {
JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error) JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error)
LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error) LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error)
CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, 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 { type JoinGroupWithLinkRequest struct {
@ -25,6 +28,7 @@ type CreateGroupRequest struct {
type ParticipantRequest struct { type ParticipantRequest struct {
GroupID string `json:"group_id" form:"group_id"` GroupID string `json:"group_id" form:"group_id"`
Participants []string `json:"participants" form:"participants"` Participants []string `json:"participants" form:"participants"`
Action whatsmeow.ParticipantChange `json:"action" form:"action"`
} }
type ParticipantStatus struct { 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/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.mau.fi/whatsmeow"
) )
type Group struct { 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/join-with-link", rest.JoinGroupWithLink)
app.Post("/group/leave", rest.LeaveGroup) app.Post("/group/leave", rest.LeaveGroup)
app.Post("/group/participants", rest.AddParticipants) 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 return rest
} }
@ -81,7 +85,9 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error {
whatsapp.SanitizePhone(&request.GroupID) 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) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
@ -91,3 +97,63 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error {
Results: result, 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() 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) err = os.WriteFile(extractedMedia.MediaPath, data, 0600)
if err != nil { if err != nil {
return extractedMedia, err 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 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 { if err = validations.ValidateParticipant(ctx, request); err != nil {
return result, err return result, err
} }
@ -89,7 +89,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr
return result, err return result, err
} }
participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, whatsmeow.ParticipantChangeAdd)
participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, request.Action)
if err != nil { if err != nil {
return result, err return result, err
} }
@ -105,7 +105,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr
result = append(result, domainGroup.ParticipantStatus{ result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(), Participant: participant.JID.String(),
Status: "success", 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 { export default {
name: 'AddParticipantsToGroup',
name: 'ManageGroupParticipants',
data() { data() {
return { return {
loading: false, loading: false,
group: '', group: '',
action: 'add', // add, remove, promote, demote
participants: ['', ''], participants: ['', ''],
}
};
}, },
computed: { computed: {
group_id() { group_id() {
return `${this.group}@${window.TYPEGROUP}`
}
return `${this.group}@${window.TYPEGROUP}`;
},
}, },
methods: { methods: {
openModal() { openModal() {
$('#modalGroupAddParticipant').modal({ $('#modalGroupAddParticipant').modal({
onApprove: function () {
onApprove: function() {
return false; return false;
}
},
}).modal('show'); }).modal('show');
}, },
handleAddParticipant() { handleAddParticipant() {
this.participants.push('')
this.participants.push('');
}, },
handleDeleteParticipant(index) { handleDeleteParticipant(index) {
this.participants.splice(index, 1)
this.participants.splice(index, 1);
}, },
async handleSubmit() { async handleSubmit() {
try { try {
let response = await this.submitApi()
showSuccessInfo(response)
let response = await this.submitApi();
showSuccessInfo(response);
$('#modalGroupAddParticipant').modal('hide'); $('#modalGroupAddParticipant').modal('hide');
} catch (err) { } catch (err) {
showErrorInfo(err)
showErrorInfo(err);
} }
}, },
async submitApi() { async submitApi() {
this.loading = true; this.loading = true;
try { try {
let response = await window.http.post(`/group/participants`, {
const payload = {
group_id: this.group_id, group_id: this.group_id,
// convert participant become list of string // 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(); this.handleReset();
return response.data.message; return response.data.message;
} catch (error) { } catch (error) {
@ -56,6 +74,7 @@ export default {
}, },
handleReset() { handleReset() {
this.group = ''; this.group = '';
this.action = 'add';
this.participants = ['', '']; this.participants = ['', ''];
}, },
}, },
@ -63,9 +82,9 @@ export default {
<div class="green card" @click="openModal" style="cursor: pointer"> <div class="green card" @click="openModal" style="cursor: pointer">
<div class="content"> <div class="content">
<a class="ui green right ribbon label">Group</a> <a class="ui green right ribbon label">Group</a>
<div class="header">Add Participants</div>
<div class="header">Manage Participants</div>
<div class="description"> <div class="description">
Add multiple participants
Add/Remove/Promote/Demote Participants
</div> </div>
</div> </div>
</div> </div>
@ -74,7 +93,7 @@ export default {
<div class="ui small modal" id="modalGroupAddParticipant"> <div class="ui small modal" id="modalGroupAddParticipant">
<i class="close icon"></i> <i class="close icon"></i>
<div class="header"> <div class="header">
Add Participants
Manage Participants
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
@ -105,15 +124,25 @@ export default {
</div> </div>
</div> </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> </form>
</div> </div>
<div class="actions"> <div class="actions">
<div class="ui approve positive right labeled icon button" :class="{'loading': this.loading}" <div class="ui approve positive right labeled icon button" :class="{'loading': this.loading}"
@click="handleSubmit" type="button"> @click="handleSubmit" type="button">
Create
Submit
<i class="send icon"></i> <i class="send icon"></i>
</div> </div>
</div> </div>
</div> </div>
`
}
`,
};

2
src/views/index.html

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

Loading…
Cancel
Save