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 @@ [![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!** -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) @@ -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 { -
- + @@ -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) => {