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..0d9553f 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"
diff --git a/src/domains/group/group.go b/src/domains/group/group.go
index 0cf2909..36e8db4 100644
--- a/src/domains/group/group.go
+++ b/src/domains/group/group.go
@@ -2,6 +2,7 @@ package group
import (
"context"
+ "time"
"go.mau.fi/whatsmeow"
)
@@ -11,7 +12,7 @@ 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 []string, err error)
+ GetGroupRequestParticipants(ctx context.Context, request GetGroupRequestParticipantsRequest) (result []GetGroupRequestParticipantsResponse, err error)
ManageGroupRequestParticipants(ctx context.Context, request GroupRequestParticipantsRequest) (result []ParticipantStatus, err error)
}
@@ -44,6 +45,11 @@ 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"`
diff --git a/src/services/group.go b/src/services/group.go
index b2710cf..ac99d79 100644
--- a/src/services/group.go
+++ b/src/services/group.go
@@ -115,7 +115,7 @@ func (service groupService) ManageParticipant(ctx context.Context, request domai
return result, nil
}
-func (service groupService) GetGroupRequestParticipants(ctx context.Context, request domainGroup.GetGroupRequestParticipantsRequest) (result []string, err error) {
+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
}
@@ -131,7 +131,10 @@ func (service groupService) GetGroupRequestParticipants(ctx context.Context, req
}
for _, participant := range participants {
- result = append(result, participant.JID.String())
+ result = append(result, domainGroup.GetGroupRequestParticipantsResponse{
+ JID: participant.JID.String(),
+ RequestedAt: participant.RequestedAt,
+ })
}
return result, 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..615a211 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,93 @@ 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/participants/requested?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 handleApproveRequest(member) {
+ if (!this.selectedGroupId || !member) return;
+
+ try {
+ this.processingMember = member.jid;
+
+ const payload = {
+ group_id: this.selectedGroupId,
+ participants: [this.formatJID(member.jid)]
+ };
+
+ await window.http.post('/group/participants/requested/approve', payload);
+
+ // Remove the approved member from the list
+ this.requestedMembers = this.requestedMembers.filter(m => m.jid !== member.jid);
+
+ showSuccessInfo("Member request approved");
+ this.processingMember = null;
+
+ } catch (error) {
+ this.processingMember = null;
+ let errorMessage = "Failed to approve member request";
+ if (error.response) {
+ errorMessage = error.response.data.message || errorMessage;
+ }
+ showErrorInfo(errorMessage);
+ }
+ },
+ async handleRejectRequest(member) {
+ if (!this.selectedGroupId || !member) return;
+
+ try {
+ this.processingMember = member.jid;
+
+ const payload = {
+ group_id: this.selectedGroupId,
+ participants: [this.formatJID(member.jid)]
+ };
+
+ await window.http.post('/group/participants/requested/reject', payload);
+
+ // Remove the rejected member from the list
+ this.requestedMembers = this.requestedMembers.filter(m => m.jid !== member.jid);
+
+ showSuccessInfo("Member request rejected");
+ this.processingMember = null;
+
+ } catch (error) {
+ this.processingMember = null;
+ let errorMessage = "Failed to reject member request";
+ if (error.response) {
+ errorMessage = error.response.data.message || errorMessage;
+ }
+ showErrorInfo(errorMessage);
+ }
}
},
template: `
@@ -81,7 +180,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) => {