diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 0ba4426..3b34482 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -1,7 +1,7 @@
openapi: "3.0.0"
info:
title: WhatsApp API MultiDevice
- version: 5.3.0
+ version: 5.4.0
description: This API is used for sending whatsapp via API
servers:
- url: http://localhost:3000
@@ -18,6 +18,9 @@ tags:
description: Group setting
- name: newsletter
description: newsletter setting
+security:
+ - basicAuth: []
+
paths:
/app/login:
get:
@@ -1217,6 +1220,123 @@ paths:
application/json:
schema:
$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:
post:
operationId: leaveGroup
@@ -1287,6 +1407,10 @@ paths:
$ref: '#/components/schemas/ErrorInternalServer'
components:
+ securitySchemes:
+ basicAuth:
+ type: http
+ scheme: basic
schemas:
CreateGroupResponse:
type: object
@@ -1319,6 +1443,7 @@ components:
- '6839241294719274'
ManageParticipantResponse:
type: object
+ additionalProperties: false
properties:
code:
type: string
@@ -1329,6 +1454,8 @@ components:
results:
type: array
items:
+ type: object
+ additionalProperties: false
properties:
participant:
type: string
@@ -1851,4 +1978,29 @@ components:
example: 0
AddRequest:
type: string
- example: null
\ No newline at end of file
+ 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"
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 488bad9..7c25343 100644
--- a/readme.md
+++ b/readme.md
@@ -2,7 +2,7 @@
[](https://www.patreon.com/c/aldinokemal)
**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!
___

@@ -44,13 +44,14 @@ Now that we support ARM64 for Linux:
- `-w="http://yourwebhook.site/handler"`
- Webhook Secret
Our webhook will be sent to you with an HMAC header and a sha256 default key `secret`.
-
+
You may modify this by using the option below:
- `--webhook-secret="secret"`
## 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)
2. Environment variables
@@ -181,45 +182,48 @@ You can fork or edit this source code !
- Use [SwaggerEditor](https://editor.swagger.io) to visualize the API.
- Generate HTTP clients using [openapi-generator](https://openapi-generator.tech/#try).
-| Feature | Menu | Method | URL |
-|---------|------------------------------|--------|-------------------------------|
-| ✅ | Login with Scan QR | GET | /app/login |
-| ✅ | Login With Pair Code | GET | /app/login-with-code |
-| ✅ | Logout | GET | /app/logout |
-| ✅ | Reconnect | GET | /app/reconnect |
-| ✅ | Devices | GET | /app/devices |
-| ✅ | User Info | GET | /user/info |
-| ✅ | User Avatar | GET | /user/avatar |
-| ✅ | User Change Avatar | POST | /user/avatar |
-| ✅ | User Change PushName | POST | /user/pushname |
-| ✅ | User My Groups | GET | /user/my/groups |
-| ✅ | User My Newsletter | GET | /user/my/newsletters |
-| ✅ | User My Privacy Setting | GET | /user/my/privacy |
-| ✅ | User My Contacts | GET | /user/my/contacts |
-| ✅ | Send Message | POST | /send/message |
-| ✅ | Send Image | POST | /send/image |
-| ✅ | Send Audio | POST | /send/audio |
-| ✅ | Send File | POST | /send/file |
-| ✅ | Send Video | POST | /send/video |
-| ✅ | Send Contact | POST | /send/contact |
-| ✅ | Send Link | POST | /send/link |
-| ✅ | Send Location | POST | /send/location |
-| ✅ | Send Poll / Vote | POST | /send/poll |
-| ✅ | Send Presence | POST | /send/presence |
-| ✅ | Revoke Message | POST | /message/:message_id/revoke |
-| ✅ | React Message | POST | /message/:message_id/reaction |
-| ✅ | Delete Message | POST | /message/:message_id/delete |
-| ✅ | Edit Message | POST | /message/:message_id/update |
-| ✅ | Read Message (DM) | POST | /message/:message_id/read |
-| ❌ | 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 | POST | /group/participants/remove |
-| ✅ | Promote Participant in Group | POST | /group/participants/promote |
-| ✅ | Demote Participant in Group | POST | /group/participants/demote |
-| ✅ | Unfollow Newsletter | POST | /newsletter/unfollow |
+| Feature | Menu | Method | URL |
+|---------|----------------------------------------|--------|---------------------------------------|
+| ✅ | Login with Scan QR | GET | /app/login |
+| ✅ | Login With Pair Code | GET | /app/login-with-code |
+| ✅ | Logout | GET | /app/logout |
+| ✅ | Reconnect | GET | /app/reconnect |
+| ✅ | Devices | GET | /app/devices |
+| ✅ | User Info | GET | /user/info |
+| ✅ | User Avatar | GET | /user/avatar |
+| ✅ | User Change Avatar | POST | /user/avatar |
+| ✅ | User Change PushName | POST | /user/pushname |
+| ✅ | User My Groups | GET | /user/my/groups |
+| ✅ | User My Newsletter | GET | /user/my/newsletters |
+| ✅ | User My Privacy Setting | GET | /user/my/privacy |
+| ✅ | User My Contacts | GET | /user/my/contacts |
+| ✅ | Send Message | POST | /send/message |
+| ✅ | Send Image | POST | /send/image |
+| ✅ | Send Audio | POST | /send/audio |
+| ✅ | Send File | POST | /send/file |
+| ✅ | Send Video | POST | /send/video |
+| ✅ | Send Contact | POST | /send/contact |
+| ✅ | Send Link | POST | /send/link |
+| ✅ | Send Location | POST | /send/location |
+| ✅ | Send Poll / Vote | POST | /send/poll |
+| ✅ | Send Presence | POST | /send/presence |
+| ✅ | Revoke Message | POST | /message/:message_id/revoke |
+| ✅ | React Message | POST | /message/:message_id/reaction |
+| ✅ | Delete Message | POST | /message/:message_id/delete |
+| ✅ | Edit Message | POST | /message/:message_id/update |
+| ✅ | Read Message (DM) | POST | /message/:message_id/read |
+| ✅ | 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 | POST | /group/participants/remove |
+| ✅ | Promote Participant in Group | POST | /group/participants/promote |
+| ✅ | 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 |
```txt
✅ = Available
@@ -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)
`export CGO_CFLAGS_ALLOW="-Xpreprocessor"`
+
+## Important
+
+- This project is unofficial and not affiliated with WhatsApp.
+- Please use official WhatsApp API to avoid any issues.
diff --git a/src/config/settings.go b/src/config/settings.go
index 712a431..7815f1d 100644
--- a/src/config/settings.go
+++ b/src/config/settings.go
@@ -5,7 +5,7 @@ import (
)
var (
- AppVersion = "v5.5.0"
+ AppVersion = "v5.6.0"
AppPort = "3000"
AppDebug = false
AppOs = "AldinoKemal"
@@ -19,7 +19,7 @@ var (
PathStorages = "storages"
PathChatStorage = "storages/chat.csv"
- DBURI = "file:storages/whatsapp.db?_foreign_keys=off"
+ DBURI = "file:storages/whatsapp.db?_foreign_keys=on"
WhatsappAutoReplyMessage string
WhatsappWebhook []string
diff --git a/src/domains/group/group.go b/src/domains/group/group.go
index 59d6764..36e8db4 100644
--- a/src/domains/group/group.go
+++ b/src/domains/group/group.go
@@ -2,6 +2,8 @@ package group
import (
"context"
+ "time"
+
"go.mau.fi/whatsmeow"
)
@@ -10,6 +12,8 @@ type IGroupService interface {
LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error)
CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, 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 {
@@ -36,3 +40,18 @@ type ParticipantStatus struct {
Status string `json:"status"`
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"`
+}
diff --git a/src/go.mod b/src/go.mod
index a5f24f0..dda8e1a 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -3,7 +3,7 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice
go 1.24.0
require (
- github.com/PuerkitoBio/goquery v1.10.2
+ github.com/PuerkitoBio/goquery v1.10.3
github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1
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/google/uuid v1.6.0
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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.9.1
@@ -20,8 +20,8 @@ require (
github.com/stretchr/testify v1.10.0
github.com/valyala/fasthttp v1.60.0
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
)
@@ -42,12 +42,13 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.34.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/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
@@ -56,8 +57,9 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.mau.fi/util v0.8.6 // 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/text v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/src/go.sum b/src/go.sum
index 5fd4c2b..b2b1136 100644
--- a/src/go.sum
+++ b/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=
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.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/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
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.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.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/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/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-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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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-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-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/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
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.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
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.8.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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/src/internal/rest/group.go b/src/internal/rest/group.go
index 53f98bd..b365862 100644
--- a/src/internal/rest/group.go
+++ b/src/internal/rest/group.go
@@ -2,6 +2,7 @@ package rest
import (
"fmt"
+
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
@@ -22,6 +23,9 @@ func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group {
app.Post("/group/participants/remove", rest.DeleteParticipants)
app.Post("/group/participants/promote", rest.PromoteParticipants)
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
}
@@ -77,83 +81,86 @@ func (controller *Group) CreateGroup(c *fiber.Ctx) error {
},
})
}
-
func (controller *Group) AddParticipants(c *fiber.Ctx) error {
- var request domainGroup.ParticipantRequest
- err := c.BodyParser(&request)
- utils.PanicIfNeeded(err)
-
- whatsapp.SanitizePhone(&request.GroupID)
+ 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)
- 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)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
- Message: "Success delete participants",
+ Message: "Success getting list requested participants",
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
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
-
whatsapp.SanitizePhone(&request.GroupID)
-
- request.Action = whatsmeow.ParticipantChangePromote
-
+ request.Action = action
result, err := controller.Service.ManageParticipant(c.UserContext(), request)
utils.PanicIfNeeded(err)
-
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
- Message: "Success promote participants",
+ Message: successMsg,
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)
utils.PanicIfNeeded(err)
-
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)
-
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
- Message: "Success demote participants",
+ Message: successMsg,
Results: result,
})
}
diff --git a/src/services/group.go b/src/services/group.go
index afe2aa1..2c31bb6 100644
--- a/src/services/group.go
+++ b/src/services/group.go
@@ -2,6 +2,8 @@ package services
import (
"context"
+ "fmt"
+
"github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
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
}
+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) {
var participantsJID []types.JID
for _, participant := range participants {
diff --git a/src/services/message.go b/src/services/message.go
index 1a9eaaa..9d16da1 100644
--- a/src/services/message.go
+++ b/src/services/message.go
@@ -5,7 +5,6 @@ import (
"fmt"
"time"
- "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/validations"
@@ -55,7 +54,7 @@ func (service serviceMessage) MarkAsRead(ctx context.Context, request domainMess
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 {
return response, err
}
diff --git a/src/services/user.go b/src/services/user.go
index b49562d..12e7f1f 100644
--- a/src/services/user.go
+++ b/src/services/user.go
@@ -233,8 +233,7 @@ func (service userService) ChangeAvatar(ctx context.Context, request domainUser.
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)
err = service.WaCli.SendAppState(appstate.BuildSettingPushName(request.PushName))
diff --git a/src/validations/group_validation.go b/src/validations/group_validation.go
index 0758c1f..cd6ab5a 100644
--- a/src/validations/group_validation.go
+++ b/src/validations/group_validation.go
@@ -2,9 +2,11 @@ package validations
import (
"context"
+
domainGroup "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/group"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
validation "github.com/go-ozzo/ozzo-validation/v4"
+ "go.mau.fi/whatsmeow"
)
func ValidateJoinGroupWithLink(ctx context.Context, request domainGroup.JoinGroupWithLinkRequest) error {
@@ -58,3 +60,30 @@ func ValidateParticipant(ctx context.Context, request domainGroup.ParticipantReq
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
+}
diff --git a/src/views/assets/app.css b/src/views/assets/app.css
index 7b7fe6f..9c41e99 100644
--- a/src/views/assets/app.css
+++ b/src/views/assets/app.css
@@ -204,7 +204,7 @@ body {
border-radius: 12px !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: var(--primary-color) !important;
+ /* background: var(--primary-color) !important; */
color: white !important;
font-weight: 600 !important;
letter-spacing: 0.5px;
diff --git a/src/views/components/GroupList.js b/src/views/components/GroupList.js
index 38cb2d5..3fe44c2 100644
--- a/src/views/components/GroupList.js
+++ b/src/views/components/GroupList.js
@@ -1,8 +1,20 @@
export default {
name: 'ListGroup',
+ props: ['connected'],
data() {
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: {
@@ -67,6 +79,69 @@ export default {
formatDate: function (value) {
if (!value) return ''
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: `
@@ -81,7 +156,7 @@ export default {
-
+
+
+
+
+
+
+
+
+
+
+
+
There are no pending member requests for this group.
+
+
+
+
+
+ | User ID |
+ Request Time |
+ Action |
+
+
+
+
+ | {{ formatJID(member.jid) }} |
+ {{ formatDate(member.requested_at) }} |
+
+
+ |
+
+
+
+
+
+
`
}
\ No newline at end of file
diff --git a/src/views/index.html b/src/views/index.html
index b79dcc4..7635eea 100644
--- a/src/views/index.html
+++ b/src/views/index.html
@@ -136,7 +136,7 @@
-
+
@@ -218,12 +218,12 @@
import GroupCreate from "./components/GroupCreate.js";
import GroupJoinWithLink from "./components/GroupJoinWithLink.js";
import GroupAddParticipants from "./components/GroupManageParticipants.js";
+ import NewsletterList from "./components/NewsletterList.js";
import AccountAvatar from "./components/AccountAvatar.js";
import AccountChangeAvatar from "./components/AccountChangeAvatar.js";
import AccountChangePushName from "./components/AccountChangePushName.js";
import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js";
- import NewsletterList from "./components/NewsletterList.js";
import AccountContact from "./components/AccountContact.js";
const showErrorInfo = (message) => {