Browse Source

feat: manage participant

pull/169/head
Aldino Kemal 2 years ago
parent
commit
558983f9c7
  1. 7
      readme.md
  2. 2
      src/config/settings.go
  3. 12
      src/domains/group/group.go
  4. 68
      src/internal/rest/group.go
  5. 11
      src/pkg/whatsapp/whatsapp.go
  6. 6
      src/services/group.go
  7. 69
      src/views/components/GroupManageParticipants.js
  8. 2
      src/views/index.html

7
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

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"

12
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 {
@ -23,8 +26,9 @@ type CreateGroupRequest struct {
} }
type ParticipantRequest 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 { 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.Delete("/group/participants", rest.DeleteParticipants)
app.Patch("/group/participants/promote", rest.PromoteParticipants)
app.Patch("/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.delete(`/group/participants`, { data: payload });
break;
case 'promote':
response = await window.http.patch(`/group/participants/promote`, payload);
break;
case 'demote':
response = await window.http.patch(`/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