Browse Source

feat: approval requested participant group (#276)

* feat: Add group participant request management functionality

- Introduced new endpoints for handling requested participants in groups:
  - GET /group/participants/requested to retrieve requested participants.
  - POST /group/participants/requested/approve to approve participant requests.
  - POST /group/participants/requested/reject to reject participant requests.

- Added corresponding methods in the group service to manage participant requests.
- Implemented validation for new request types to ensure proper data handling.

* feat: Update API version and enhance group participant request management

- Bumped API version from 5.3.0 to 5.4.0.
- Added new endpoints for managing group participant requests:
  - GET /group/participant-requests to retrieve pending requests.
  - POST /group/participant-requests/approve to approve requests.
  - POST /group/participant-requests/reject to reject requests.
- Updated group service methods to handle new request types and responses.
- Enhanced front-end components to support requested member management.

* chore: Update dependency versions in go.mod and go.sum

- Bumped versions for several dependencies:
  - github.com/PuerkitoBio/goquery from v1.10.2 to v1.10.3
  - github.com/mattn/go-sqlite3 from v1.14.27 to v1.14.28
  - go.mau.fi/whatsmeow to a new version
  - golang.org/x/image from v0.25.0 to v0.26.0
  - golang.org/x/net from v0.38.0 to v0.39.0
  - github.com/pelletier/go-toml/v2 from v2.2.3 to v2.2.4
  - golang.org/x/crypto from v0.36.0 to v0.37.0
  - Added new indirect dependencies for petermattis/goid and updated existing ones.

* refactor: Change receiver type for ChangePushName method in userService

* refactor: Rename group participant request endpoints and streamline request handling

- Updated API endpoints for managing group participant requests:
  - Changed GET /group/participants/requested to GET /group/participant-requests
  - Changed POST /group/participants/requested/approve to POST /group/participant-requests/approve
  - Changed POST /group/participants/requested/reject to POST /group/participant-requests/reject
- Refactored front-end methods to handle the new endpoint structure and consolidate approval/rejection logic.

* feat: Add validation for empty Group ID in participant request listing

- Implemented a check to ensure Group ID is not empty in the ListParticipantRequests method.
- Returns a 400 Bad Request response with an appropriate error message if Group ID is missing.

* feat: Enhance participant request management with error handling

- Updated ManageGroupRequestParticipants method to include error handling for participant requests.
- Added validation for the Action field in participant management requests to ensure it is not empty and is one of the allowed values.

* refactor: Update message handling to use domainMessage consistently

- Removed redundant import of message package.
- Updated ReactMessage method to use domainMessage types for request and response, ensuring consistency across the service.
pull/280/head v5.6.0
Aldino Kemal 11 months ago
committed by GitHub
parent
commit
d43aa43e3d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 154
      docs/openapi.yaml
  2. 17
      readme.md
  3. 4
      src/config/settings.go
  4. 19
      src/domains/group/group.go
  5. 18
      src/go.mod
  6. 20
      src/go.sum
  7. 83
      src/internal/rest/group.go
  8. 66
      src/services/group.go
  9. 3
      src/services/message.go
  10. 3
      src/services/user.go
  11. 29
      src/validations/group_validation.go
  12. 2
      src/views/assets/app.css
  13. 134
      src/views/components/GroupList.js
  14. 4
      src/views/index.html

154
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: 5.3.0
version: 5.4.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
@ -18,6 +18,9 @@ tags:
description: Group setting description: Group setting
- name: newsletter - name: newsletter
description: newsletter setting description: newsletter setting
security:
- basicAuth: []
paths: paths:
/app/login: /app/login:
get: get:
@ -1217,6 +1220,123 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ErrorInternalServer' $ref: '#/components/schemas/ErrorInternalServer'
/group/participant-requests:
get:
operationId: getGroupParticipantRequests
tags:
- group
summary: Get list of participant requests to join group
parameters:
- name: group_id
in: query
required: true
schema:
type: string
example: '120363024512399999@g.us'
description: The group ID to get participant requests for
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/GroupParticipantRequestListResponse'
'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/participant-requests/approve:
post:
operationId: approveGroupParticipantRequest
tags:
- group
summary: Approve participant request to join group
requestBody:
content:
application/json:
schema:
type: object
properties:
group_id:
type: string
example: '120363024512399999@g.us'
description: The group ID
participant_id:
type: string
example: '6281234567890'
description: The participant's WhatsApp ID to approve
required:
- group_id
- participant_id
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/GenericResponse'
'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/participant-requests/reject:
post:
operationId: rejectGroupParticipantRequest
tags:
- group
summary: Reject participant request to join group
requestBody:
content:
application/json:
schema:
type: object
properties:
group_id:
type: string
example: '120363024512399999@g.us'
description: The group ID
participant_id:
type: string
example: '6281234567890'
description: The participant's WhatsApp ID to reject
required:
- group_id
- participant_id
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/GenericResponse'
'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/leave: /group/leave:
post: post:
operationId: leaveGroup operationId: leaveGroup
@ -1287,6 +1407,10 @@ paths:
$ref: '#/components/schemas/ErrorInternalServer' $ref: '#/components/schemas/ErrorInternalServer'
components: components:
securitySchemes:
basicAuth:
type: http
scheme: basic
schemas: schemas:
CreateGroupResponse: CreateGroupResponse:
type: object type: object
@ -1319,6 +1443,7 @@ components:
- '6839241294719274' - '6839241294719274'
ManageParticipantResponse: ManageParticipantResponse:
type: object type: object
additionalProperties: false
properties: properties:
code: code:
type: string type: string
@ -1329,6 +1454,8 @@ components:
results: results:
type: array type: array
items: items:
type: object
additionalProperties: false
properties: properties:
participant: participant:
type: string type: string
@ -1852,3 +1979,28 @@ components:
AddRequest: AddRequest:
type: string type: string
example: null example: null
GroupParticipantRequestListResponse:
type: object
properties:
code:
type: string
example: "SUCCESS"
message:
type: string
example: "Success getting list requested participants"
results:
type: object
properties:
data:
type: array
items:
type: object
properties:
jid:
type: string
example: "6289685024091@s.whatsapp.net"
requested_at:
type: string
format: date-time
example: "2024-10-11T21:27:29+07:00"

17
readme.md

@ -2,7 +2,7 @@
[![Patreon](https://img.shields.io/badge/Support%20on-Patreon-orange.svg)](https://www.patreon.com/c/aldinokemal) [![Patreon](https://img.shields.io/badge/Support%20on-Patreon-orange.svg)](https://www.patreon.com/c/aldinokemal)
**If you're using this tools to generate income, consider supporting its development by becoming a Patreon member!** **If you're using this tools to generate income, consider supporting its development by becoming a Patreon member!**
Your support helps ensure the library stays maintained and receives regular updates. Join our community of supporters today!
Your support helps ensure the library stays maintained and receives regular updates!
___ ___
![release version](https://img.shields.io/github/v/release/aldinokemal/go-whatsapp-web-multidevice) ![release version](https://img.shields.io/github/v/release/aldinokemal/go-whatsapp-web-multidevice)
@ -50,7 +50,8 @@ Now that we support ARM64 for Linux:
## Configuration ## Configuration
You can configure the application using either command-line flags (shown above) or environment variables. Configuration can be set in three ways (in order of priority):
You can configure the application using either command-line flags (shown above) or environment variables. Configuration
can be set in three ways (in order of priority):
1. Command-line flags (highest priority) 1. Command-line flags (highest priority)
2. Environment variables 2. Environment variables
@ -182,7 +183,7 @@ You can fork or edit this source code !
- Generate HTTP clients using [openapi-generator](https://openapi-generator.tech/#try). - Generate HTTP clients using [openapi-generator](https://openapi-generator.tech/#try).
| Feature | Menu | Method | URL | | Feature | Menu | Method | URL |
|---------|------------------------------|--------|-------------------------------|
|---------|----------------------------------------|--------|---------------------------------------|
| ✅ | Login with Scan QR | GET | /app/login | | ✅ | Login with Scan QR | GET | /app/login |
| ✅ | Login With Pair Code | GET | /app/login-with-code | | ✅ | Login With Pair Code | GET | /app/login-with-code |
| ✅ | Logout | GET | /app/logout | | ✅ | Logout | GET | /app/logout |
@ -211,7 +212,7 @@ You can fork or edit this source code !
| ✅ | 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 |
| ✅ | Read Message (DM) | POST | /message/:message_id/read | | ✅ | Read Message (DM) | POST | /message/:message_id/read |
| ❌ | Star Message | POST | /message/:message_id/star |
| ✅ | 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 |
@ -219,6 +220,9 @@ You can fork or edit this source code !
| ✅ | Remove Participant in Group | POST | /group/participants/remove | | ✅ | Remove Participant in Group | POST | /group/participants/remove |
| ✅ | Promote Participant in Group | POST | /group/participants/promote | | ✅ | Promote Participant in Group | POST | /group/participants/promote |
| ✅ | Demote Participant in Group | POST | /group/participants/demote | | ✅ | Demote Participant in Group | POST | /group/participants/demote |
| ✅ | List Requested Participants in Group | POST | /group/participants/requested |
| ✅ | Approve Requested Participant in Group | POST | /group/participants/requested/approve |
| ✅ | Reject Requested Participant in Group | POST | /group/participants/requested/reject |
| ✅ | Unfollow Newsletter | POST | /newsletter/unfollow | | ✅ | Unfollow Newsletter | POST | /newsletter/unfollow |
```txt ```txt
@ -261,3 +265,8 @@ You can fork or edit this source code !
- Please do this if you have an error (invalid flag in pkg-config --cflags: -Xpreprocessor) - Please do this if you have an error (invalid flag in pkg-config --cflags: -Xpreprocessor)
`export CGO_CFLAGS_ALLOW="-Xpreprocessor"` `export CGO_CFLAGS_ALLOW="-Xpreprocessor"`
## Important
- This project is unofficial and not affiliated with WhatsApp.
- Please use official WhatsApp API to avoid any issues.

4
src/config/settings.go

@ -5,7 +5,7 @@ import (
) )
var ( var (
AppVersion = "v5.5.0"
AppVersion = "v5.6.0"
AppPort = "3000" AppPort = "3000"
AppDebug = false AppDebug = false
AppOs = "AldinoKemal" AppOs = "AldinoKemal"
@ -19,7 +19,7 @@ var (
PathStorages = "storages" PathStorages = "storages"
PathChatStorage = "storages/chat.csv" PathChatStorage = "storages/chat.csv"
DBURI = "file:storages/whatsapp.db?_foreign_keys=off"
DBURI = "file:storages/whatsapp.db?_foreign_keys=on"
WhatsappAutoReplyMessage string WhatsappAutoReplyMessage string
WhatsappWebhook []string WhatsappWebhook []string

19
src/domains/group/group.go

@ -2,6 +2,8 @@ package group
import ( import (
"context" "context"
"time"
"go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow"
) )
@ -10,6 +12,8 @@ type IGroupService interface {
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)
ManageParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error) ManageParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error)
GetGroupRequestParticipants(ctx context.Context, request GetGroupRequestParticipantsRequest) (result []GetGroupRequestParticipantsResponse, err error)
ManageGroupRequestParticipants(ctx context.Context, request GroupRequestParticipantsRequest) (result []ParticipantStatus, err error)
} }
type JoinGroupWithLinkRequest struct { type JoinGroupWithLinkRequest struct {
@ -36,3 +40,18 @@ type ParticipantStatus struct {
Status string `json:"status"` Status string `json:"status"`
Message string `json:"message"` Message string `json:"message"`
} }
type GetGroupRequestParticipantsRequest struct {
GroupID string `json:"group_id" query:"group_id"`
}
type GetGroupRequestParticipantsResponse struct {
JID string `json:"jid"`
RequestedAt time.Time `json:"requested_at"`
}
type GroupRequestParticipantsRequest struct {
GroupID string `json:"group_id" form:"group_id"`
Participants []string `json:"participants" form:"participants"`
Action whatsmeow.ParticipantRequestChange `json:"action" form:"action"`
}

18
src/go.mod

@ -3,7 +3,7 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice
go 1.24.0 go 1.24.0
require ( require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/PuerkitoBio/goquery v1.10.3
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/go-ozzo/ozzo-validation/v4 v4.3.0
@ -12,7 +12,7 @@ require (
github.com/gofiber/websocket/v2 v2.2.1 github.com/gofiber/websocket/v2 v2.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.27
github.com/mattn/go-sqlite3 v1.14.28
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
@ -20,8 +20,8 @@ require (
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/valyala/fasthttp v1.60.0 github.com/valyala/fasthttp v1.60.0
go.mau.fi/libsignal v0.1.2 go.mau.fi/libsignal v0.1.2
go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088
golang.org/x/image v0.25.0
go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526
golang.org/x/image v0.26.0
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
) )
@ -42,12 +42,13 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.34.0 // indirect github.com/rs/zerolog v1.34.0 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.7.1 // indirect
@ -56,8 +57,9 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
go.mau.fi/util v0.8.6 // indirect go.mau.fi/util v0.8.6 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

20
src/go.sum

@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@ -71,8 +73,14 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q=
github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
@ -92,6 +100,8 @@ github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFT
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
@ -134,6 +144,8 @@ go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82 h1:AZlDkXHgoQNW4gd2hnTCvP
go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= go.mau.fi/whatsmeow v0.0.0-20250318233852-06705625cf82/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs=
go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088 h1:ns6nk2NjqdaQnCKrp+Qqwpf+3OI7+nnH56D71+7XzOM= go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088 h1:ns6nk2NjqdaQnCKrp+Qqwpf+3OI7+nnH56D71+7XzOM=
go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= go.mau.fi/whatsmeow v0.0.0-20250402091807-b0caa1b76088/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs=
go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 h1:i9w16FdM3zmOWdF5nh1l2MlmE/wK7ulL6rbT02WBBJs=
go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -144,9 +156,15 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -165,6 +183,8 @@ golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

83
src/internal/rest/group.go

@ -2,6 +2,7 @@ package rest
import ( import (
"fmt" "fmt"
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" 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/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
@ -22,6 +23,9 @@ func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group {
app.Post("/group/participants/remove", rest.DeleteParticipants) app.Post("/group/participants/remove", rest.DeleteParticipants)
app.Post("/group/participants/promote", rest.PromoteParticipants) app.Post("/group/participants/promote", rest.PromoteParticipants)
app.Post("/group/participants/demote", rest.DemoteParticipants) app.Post("/group/participants/demote", rest.DemoteParticipants)
app.Get("/group/participant-requests", rest.ListParticipantRequests)
app.Post("/group/participant-requests/approve", rest.ApproveParticipantRequests)
app.Post("/group/participant-requests/reject", rest.RejectParticipantRequests)
return rest return rest
} }
@ -77,83 +81,86 @@ func (controller *Group) CreateGroup(c *fiber.Ctx) error {
}, },
}) })
} }
func (controller *Group) AddParticipants(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)
return controller.manageParticipants(c, whatsmeow.ParticipantChangeAdd, "Success add participants")
}
request.Action = whatsmeow.ParticipantChangeAdd
func (controller *Group) DeleteParticipants(c *fiber.Ctx) error {
return controller.manageParticipants(c, whatsmeow.ParticipantChangeRemove, "Success delete participants")
}
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
func (controller *Group) PromoteParticipants(c *fiber.Ctx) error {
return controller.manageParticipants(c, whatsmeow.ParticipantChangePromote, "Success promote participants")
}
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success add participants",
Results: result,
})
func (controller *Group) DemoteParticipants(c *fiber.Ctx) error {
return controller.manageParticipants(c, whatsmeow.ParticipantChangeDemote, "Success demote participants")
} }
func (controller *Group) DeleteParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
err := c.BodyParser(&request)
func (controller *Group) ListParticipantRequests(c *fiber.Ctx) error {
var request domainGroup.GetGroupRequestParticipantsRequest
err := c.QueryParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID)
if request.GroupID == "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.ResponseData{
Status: 400,
Code: "INVALID_GROUP_ID",
Message: "Group ID cannot be empty",
})
}
request.Action = whatsmeow.ParticipantChangeRemove
whatsapp.SanitizePhone(&request.GroupID)
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
result, err := controller.Service.GetGroupRequestParticipants(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Status: 200, Status: 200,
Code: "SUCCESS", Code: "SUCCESS",
Message: "Success delete participants",
Message: "Success getting list requested participants",
Results: result, Results: result,
}) })
} }
func (controller *Group) PromoteParticipants(c *fiber.Ctx) error {
func (controller *Group) ApproveParticipantRequests(c *fiber.Ctx) error {
return controller.handleRequestedParticipants(c, whatsmeow.ParticipantChangeApprove, "Success approve requested participants")
}
func (controller *Group) RejectParticipantRequests(c *fiber.Ctx) error {
return controller.handleRequestedParticipants(c, whatsmeow.ParticipantChangeReject, "Success reject requested participants")
}
// Generalized participant management handler
func (controller *Group) manageParticipants(c *fiber.Ctx, action whatsmeow.ParticipantChange, successMsg string) error {
var request domainGroup.ParticipantRequest var request domainGroup.ParticipantRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID) whatsapp.SanitizePhone(&request.GroupID)
request.Action = whatsmeow.ParticipantChangePromote
request.Action = action
result, err := controller.Service.ManageParticipant(c.UserContext(), request) result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Status: 200, Status: 200,
Code: "SUCCESS", Code: "SUCCESS",
Message: "Success promote participants",
Message: successMsg,
Results: result, Results: result,
}) })
} }
func (controller *Group) DemoteParticipants(c *fiber.Ctx) error {
var request domainGroup.ParticipantRequest
// Generalized requested participants handler
func (controller *Group) handleRequestedParticipants(c *fiber.Ctx, action whatsmeow.ParticipantRequestChange, successMsg string) error {
var request domainGroup.GroupRequestParticipantsRequest
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.GroupID) whatsapp.SanitizePhone(&request.GroupID)
request.Action = whatsmeow.ParticipantChangeDemote
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
request.Action = action
result, err := controller.Service.ManageGroupRequestParticipants(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Status: 200, Status: 200,
Code: "SUCCESS", Code: "SUCCESS",
Message: "Success demote participants",
Message: successMsg,
Results: result, Results: result,
}) })
} }

66
src/services/group.go

@ -2,6 +2,8 @@ package services
import ( import (
"context" "context"
"fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
@ -113,6 +115,70 @@ func (service groupService) ManageParticipant(ctx context.Context, request domai
return result, nil return result, nil
} }
func (service groupService) GetGroupRequestParticipants(ctx context.Context, request domainGroup.GetGroupRequestParticipantsRequest) (result []domainGroup.GetGroupRequestParticipantsResponse, err error) {
if err = validations.ValidateGetGroupRequestParticipants(ctx, request); err != nil {
return result, err
}
groupJID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.GroupID)
if err != nil {
return result, err
}
participants, err := service.WaCli.GetGroupRequestParticipants(groupJID)
if err != nil {
return result, err
}
for _, participant := range participants {
result = append(result, domainGroup.GetGroupRequestParticipantsResponse{
JID: participant.JID.String(),
RequestedAt: participant.RequestedAt,
})
}
return result, nil
}
func (service groupService) ManageGroupRequestParticipants(ctx context.Context, request domainGroup.GroupRequestParticipantsRequest) (result []domainGroup.ParticipantStatus, err error) {
if err = validations.ValidateManageGroupRequestParticipants(ctx, request); err != nil {
return result, err
}
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.UpdateGroupRequestParticipants(groupJID, participantsJID, request.Action)
if err != nil {
return result, err
}
for _, participant := range participants {
if participant.Error != 0 {
result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(),
Status: "error",
Message: fmt.Sprintf("Action %s failed (code %d)", request.Action, participant.Error),
})
} else {
result = append(result, domainGroup.ParticipantStatus{
Participant: participant.JID.String(),
Status: "success",
Message: fmt.Sprintf("Action %s success", request.Action),
})
}
}
return result, nil
}
func (service groupService) participantToJID(participants []string) ([]types.JID, error) { func (service groupService) participantToJID(participants []string) ([]types.JID, error) {
var participantsJID []types.JID var participantsJID []types.JID
for _, participant := range participants { for _, participant := range participants {

3
src/services/message.go

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message"
domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations"
@ -55,7 +54,7 @@ func (service serviceMessage) MarkAsRead(ctx context.Context, request domainMess
return response, nil return response, nil
} }
func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.GenericResponse, err error) {
func (service serviceMessage) ReactMessage(ctx context.Context, request domainMessage.ReactionRequest) (response domainMessage.GenericResponse, err error) {
if err = validations.ValidateReactMessage(ctx, request); err != nil { if err = validations.ValidateReactMessage(ctx, request); err != nil {
return response, err return response, err
} }

3
src/services/user.go

@ -233,8 +233,7 @@ func (service userService) ChangeAvatar(ctx context.Context, request domainUser.
return nil return nil
} }
// ChangePushName implements user.IUserService.
func (service *userService) ChangePushName(ctx context.Context, request domainUser.ChangePushNameRequest) (err error) {
func (service userService) ChangePushName(ctx context.Context, request domainUser.ChangePushNameRequest) (err error) {
whatsapp.MustLogin(service.WaCli) whatsapp.MustLogin(service.WaCli)
err = service.WaCli.SendAppState(appstate.BuildSettingPushName(request.PushName)) err = service.WaCli.SendAppState(appstate.BuildSettingPushName(request.PushName))

29
src/validations/group_validation.go

@ -2,9 +2,11 @@ package validations
import ( import (
"context" "context"
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group" domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"go.mau.fi/whatsmeow"
) )
func ValidateJoinGroupWithLink(ctx context.Context, request domainGroup.JoinGroupWithLinkRequest) error { func ValidateJoinGroupWithLink(ctx context.Context, request domainGroup.JoinGroupWithLinkRequest) error {
@ -58,3 +60,30 @@ func ValidateParticipant(ctx context.Context, request domainGroup.ParticipantReq
return nil return nil
} }
func ValidateGetGroupRequestParticipants(ctx context.Context, request domainGroup.GetGroupRequestParticipantsRequest) error {
err := validation.ValidateStructWithContext(ctx, &request,
validation.Field(&request.GroupID, validation.Required),
)
if err != nil {
return pkgError.ValidationError(err.Error())
}
return nil
}
func ValidateManageGroupRequestParticipants(ctx context.Context, request domainGroup.GroupRequestParticipantsRequest) 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)),
validation.Field(&request.Action, validation.Required, validation.In(whatsmeow.ParticipantChangeApprove, whatsmeow.ParticipantChangeReject)),
)
if err != nil {
return pkgError.ValidationError(err.Error())
}
return nil
}

2
src/views/assets/app.css

@ -204,7 +204,7 @@ body {
border-radius: 12px !important; border-radius: 12px !important;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
/* background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important; */ /* background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important; */
background: var(--primary-color) !important;
/* background: var(--primary-color) !important; */
color: white !important; color: white !important;
font-weight: 600 !important; font-weight: 600 !important;
letter-spacing: 0.5px; letter-spacing: 0.5px;

134
src/views/components/GroupList.js

@ -1,8 +1,20 @@
export default { export default {
name: 'ListGroup', name: 'ListGroup',
props: ['connected'],
data() { data() {
return { return {
groups: []
groups: [],
selectedGroupId: null,
requestedMembers: [],
loadingRequestedMembers: false,
processingMember: null
}
},
computed: {
currentUserId() {
if (!this.connected || this.connected.length === 0) return null;
const device = this.connected[0].device;
return device.split('@')[0].split(':')[0];
} }
}, },
methods: { methods: {
@ -67,6 +79,69 @@ export default {
formatDate: function (value) { formatDate: function (value) {
if (!value) return '' if (!value) return ''
return moment(value).format('LLL'); return moment(value).format('LLL');
},
isAdmin(ownerJID) {
const owner = ownerJID.split('@')[0];
return owner === this.currentUserId;
},
async handleSeeRequestedMember(group_id) {
this.selectedGroupId = group_id;
this.loadingRequestedMembers = true;
this.requestedMembers = [];
try {
const response = await window.http.get(`/group/participant-requests?group_id=${group_id}`);
this.requestedMembers = response.data.results || [];
this.loadingRequestedMembers = false;
$('#modalRequestedMembers').modal('show');
} catch (error) {
this.loadingRequestedMembers = false;
let errorMessage = "Failed to fetch requested members";
if (error.response) {
errorMessage = error.response.data.message || errorMessage;
}
showErrorInfo(errorMessage);
}
},
formatJID(jid) {
return jid ? jid.split('@')[0] : '';
},
closeRequestedMembersModal() {
$('#modalRequestedMembers').modal('hide');
// open modal again
this.openModal();
},
async handleProcessRequest(member, action) {
if (!this.selectedGroupId || !member) return;
const actionText = action === 'approve' ? 'approve' : 'reject';
const confirmMsg = `Are you sure you want to ${actionText} this member request?`;
const ok = confirm(confirmMsg);
if (!ok) return;
try {
this.processingMember = member.jid;
const payload = {
group_id: this.selectedGroupId,
participants: [this.formatJID(member.jid)]
};
await window.http.post(`/group/participant-requests/${action}`, payload);
// Remove the processed member from the list
this.requestedMembers = this.requestedMembers.filter(m => m.jid !== member.jid);
showSuccessInfo(`Member request ${actionText}d`);
this.processingMember = null;
} catch (error) {
this.processingMember = null;
let errorMessage = `Failed to ${actionText} member request`;
if (error.response) {
errorMessage = error.response.data.message || errorMessage;
}
showErrorInfo(errorMessage);
}
} }
}, },
template: ` template: `
@ -81,7 +156,7 @@ export default {
</div> </div>
<!-- Modal AccountGroup --> <!-- Modal AccountGroup -->
<div class="ui small modal" id="modalGroupList">
<div class="ui large modal" id="modalGroupList">
<i class="close icon"></i> <i class="close icon"></i>
<div class="header"> <div class="header">
My Group List My Group List
@ -104,12 +179,67 @@ export default {
<td>{{ g.Participants.length }}</td> <td>{{ g.Participants.length }}</td>
<td>{{ formatDate(g.GroupCreated) }}</td> <td>{{ formatDate(g.GroupCreated) }}</td>
<td> <td>
<div style="display: flex; gap: 8px; align-items: center;">
<button v-if="isAdmin(g.OwnerJID)" class="ui green tiny button" @click="handleSeeRequestedMember(g.JID)">Requested Members</button>
<button class="ui red tiny button" @click="handleLeaveGroup(g.JID)">Leave</button> <button class="ui red tiny button" @click="handleLeaveGroup(g.JID)">Leave</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Requested Members Modal -->
<div class="ui modal" id="modalRequestedMembers">
<i class="close icon"></i>
<div class="header">
Requested Group Members
</div>
<div class="content">
<div v-if="loadingRequestedMembers" class="ui active centered inline loader"></div>
<div v-else-if="requestedMembers.length === 0" class="ui info message">
<div class="header">No Requested Members</div>
<p>There are no pending member requests for this group.</p>
</div>
<table v-else class="ui celled table">
<thead>
<tr>
<th>User ID</th>
<th>Request Time</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="member in requestedMembers" :key="member.jid">
<td>{{ formatJID(member.jid) }}</td>
<td>{{ formatDate(member.requested_at) }}</td>
<td>
<div class="ui mini buttons">
<button class="ui green button"
@click="handleProcessRequest(member, 'approve')"
:disabled="processingMember === member.jid">
<i v-if="processingMember === member.jid" class="spinner loading icon"></i>
Approve
</button>
<div class="or"></div>
<button class="ui red button"
@click="handleProcessRequest(member, 'reject')"
:disabled="processingMember === member.jid">
<i v-if="processingMember === member.jid" class="spinner loading icon"></i>
Reject
</button>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="actions">
<div class="ui button" @click="closeRequestedMembersModal">Close</div>
</div>
</div> </div>
` `
} }

4
src/views/index.html

@ -136,7 +136,7 @@
</div> </div>
<div class="ui three column doubling grid cards"> <div class="ui three column doubling grid cards">
<group-list></group-list>
<group-list :connected="connected_devices"></group-list>
<group-create></group-create> <group-create></group-create>
<group-join-with-link></group-join-with-link> <group-join-with-link></group-join-with-link>
<group-add-participants></group-add-participants> <group-add-participants></group-add-participants>
@ -218,12 +218,12 @@
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/GroupManageParticipants.js"; import GroupAddParticipants from "./components/GroupManageParticipants.js";
import NewsletterList from "./components/NewsletterList.js";
import AccountAvatar from "./components/AccountAvatar.js"; import AccountAvatar from "./components/AccountAvatar.js";
import AccountChangeAvatar from "./components/AccountChangeAvatar.js"; import AccountChangeAvatar from "./components/AccountChangeAvatar.js";
import AccountChangePushName from "./components/AccountChangePushName.js"; import AccountChangePushName from "./components/AccountChangePushName.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";
import NewsletterList from "./components/NewsletterList.js";
import AccountContact from "./components/AccountContact.js"; import AccountContact from "./components/AccountContact.js";
const showErrorInfo = (message) => { const showErrorInfo = (message) => {

Loading…
Cancel
Save