diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..88d8252 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: aldinokemal +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/build-docker-image.yaml b/.github/workflows/build-docker-image.yaml index 38a4290..1d30ee2 100644 --- a/.github/workflows/build-docker-image.yaml +++ b/.github/workflows/build-docker-image.yaml @@ -63,13 +63,13 @@ jobs: password: ${{ secrets.REGISTRY_PASSWORD }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Create Latest Manifest - run: | - docker buildx imagetools create -t ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest \ - ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest-amd \ - ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest-arm - name: Create Versioned Manifest (${{ github.ref_name }}) run: | docker buildx imagetools create -t ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:${{ github.ref_name }} \ ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:${{ github.ref_name }}-amd \ - ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:${{ github.ref_name }}-arm \ No newline at end of file + ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:${{ github.ref_name }}-arm + - name: Create Latest Manifest + run: | + docker buildx imagetools create -t ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest \ + ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest-amd \ + ${{ secrets.REGISTRY_USERNAME }}/go-whatsapp-web-multidevice:latest-arm \ No newline at end of file diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 2cb61e4..4b6d0ae 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Golang Installation - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.21' - name: Golang build @@ -36,7 +36,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Golang Installation - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.21' - name: Golang build diff --git a/.github/workflows/release-mac.yml b/.github/workflows/release-mac.yml index a30489f..9735f06 100644 --- a/.github/workflows/release-mac.yml +++ b/.github/workflows/release-mac.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Golang Installation - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.21' - name: Golang build diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 12e738e..bf04e4f 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Golang Installation - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.21' - name: Golang build diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index 2b84905..ea9c7af 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -1,7 +1,7 @@ ############################ # STEP 1 build executable binary ############################ -FROM golang:1.21.5-alpine3.19 AS builder +FROM golang:1.22.5-alpine3.20 AS builder RUN apk update && apk add --no-cache gcc musl-dev gcompat WORKDIR /whatsapp COPY ./src . @@ -14,7 +14,7 @@ RUN go build -o /app/whatsapp ############################# ## STEP 2 build a smaller image ############################# -FROM alpine:3.19 +FROM alpine:3.20 RUN apk update && apk add --no-cache ffmpeg WORKDIR /app # Copy compiled from builder. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 60533b8..27ce576 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 3.11.0 + version: 4.2.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -36,6 +36,32 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /app/login-with-code: + get: + operationId: appLoginWithCode + tags: + - app + summary: Login with pairing code + parameters: + - name: phone + in: query + schema: + type: string + example: '628912344551' + description: Your phone number + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LoginWithCodeResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' /app/logout: get: operationId: appLogout @@ -200,7 +226,7 @@ paths: summary: Send Message requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -427,7 +453,7 @@ paths: summary: Send Contact requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -470,7 +496,7 @@ paths: summary: Send Link requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -513,7 +539,7 @@ paths: summary: Send Location requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -618,7 +644,49 @@ paths: description: Message ID requestBody: content: - multipart/form-data: + application/json: + schema: + type: object + properties: + phone: + type: string + example: '6289685024051@s.whatsapp.net' + description: Phone number with country code + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SendResponse' + '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' + /message/{message_id}/delete: + post: + operationId: deleteMessage + tags: + - message + summary: Delete Message + parameters: + - in: path + name: message_id + schema: + type: string + required: true + description: Message ID + requestBody: + content: + application/json: schema: type: object properties: @@ -660,7 +728,7 @@ paths: description: Message ID requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -796,30 +864,104 @@ paths: content: application/json: schema: - type: object - properties: - group_id: - type: string - example: '120363228882361111' - participants: - type: array - items: - type: string - example: - - '6819241294719274' - - '6829241294719274' - - '6839241294719274' - example: - - '6819241294719274' - - '6829241294719274' - - '6839241294719274' + $ref: '#/components/schemas/ManageParticipantRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/AddParticipantToGroupResponse' + $ref: '#/components/schemas/ManageParticipantResponse' + '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/participants/remove: + post: + operationId: removeParticipantFromGroup + tags: + - group + summary: Remove participants from group + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantResponse' + '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/participants/promote: + post: + operationId: promoteParticipantToAdmin + tags: + - group + summary: Promote participants to admin + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantResponse' + '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/participants/demote: + post: + operationId: demoteParticipantToMember + tags: + - group + summary: Demote participants to member + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ManageParticipantResponse' '400': description: Bad Request content: @@ -840,7 +982,7 @@ paths: summary: Join group with link requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -874,7 +1016,7 @@ paths: summary: Leave group requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -918,7 +1060,21 @@ components: group_id: type: string example: 1203632782168851111@g.us - AddParticipantToGroupResponse: + ManageParticipantRequest: + type: object + properties: + group_id: + type: string + example: 1203632782168851111@g.us + participants: + type: array + items: + type: string + example: + - '6819241294719274' + - '6829241294719274' + - '6839241294719274' + ManageParticipantResponse: type: object properties: code: @@ -1111,6 +1267,21 @@ components: device: type: string example: '628960561XXX.0:64@s.whatsapp.net' + LoginWithCodeResponse: + type: object + properties: + code: + type: string + example: SUCCESS + message: + type: string + example: Success + results: + type: object + properties: + pair_code: + type: string + example: ABCD-1234 LoginResponse: type: object properties: diff --git a/docs/sdk/config.yaml b/docs/sdk/config.yaml index c528826..3b7b4c3 100644 --- a/docs/sdk/config.yaml +++ b/docs/sdk/config.yaml @@ -6,4 +6,8 @@ supportsES6: true withInterfaces: true # sdk-go Configs -packageName: "sdk_go_whatsapp_web_multidevice" #for golang package name \ No newline at end of file +packageName: "SdkWhatsappWebMultiDevice" + +# sdk-php Configs +composerPackageName: "SdkWhatsappWebMultiDevice" +invokerPackage: "SdkWhatsappWebMultiDevice" \ No newline at end of file diff --git a/readme.md b/readme.md index 3ce80e8..e336fbc 100644 --- a/readme.md +++ b/readme.md @@ -92,65 +92,76 @@ docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volum You can fork or edit this source code ! ### Current API + - [Api Specification Document](https://bump.sh/aldinokemal/doc/go-whatsapp-web-multidevice) -- You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API or paste to [SwaggerEditor](https://editor.swagger.io). +- You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API or paste + to [SwaggerEditor](https://editor.swagger.io). - Furthermore you can generate HTTP Client from this API using [openapi-generator](https://openapi-generator.tech/#try) -| Feature | Menu | Method | URL | -|---------|------------------------------|--------|-----------------------------| -| ✅ | Login | GET | /app/login | -| ✅ | Logout | GET | /app/logout | -| ✅ | Reconnect | GET | /app/reconnect | -| ✅ | User Info | GET | /user/info | -| ✅ | User Avatar | GET | /user/avatar | -| ✅ | User My Group List | GET | /user/my/groups | -| ✅ | User My Privacy Setting | GET | /user/my/privacy | -| ✅ | 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 | -| ✅ | Revoke Message | POST | /message/:message_id/revoke | -| ✅ | React Message | POST | /message/:message_id/react | -| ✅ | Edit Message | POST | /message/:message_id/update | -| ✅ | 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 | DELETE | /group/participants | -| ❌ | Promote Participant in Group | POST | /group/participants/promote | -| ❌ | Demote Participant in Group | POST | /group/participants/demote | +| 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 My Group List | GET | /user/my/groups | +| ✅ | User My Privacy Setting | GET | /user/my/privacy | +| ✅ | 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 | +| ✅ | 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 | +| ❌ | 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 | ``` ✅ = Available ❌ = Not Available Yet ``` -### App User Interface - -1. Homepage ![Homepage](https://i.ibb.co/Wn3H1L9/homepage.png) -2. Login ![Login](https://i.ibb.co/jkcB15R/login.png?v=1) -3. Send Message ![Send Message](https://i.ibb.co/rc3NXMX/send-message.png?v1) -4. Send Image ![Send Image](https://i.ibb.co/BcFL3SD/send-image.png?v1) -5. Send File ![Send File](https://i.ibb.co/f4yxjpp/send-file.png) -6. Send Video ![Send Video](https://i.ibb.co/PrD3P51/send-video.png) -7. Send Contact ![Send Contact](https://i.ibb.co/4810H7N/send-contact.png) -8. Send Location ![Send Location](https://i.ibb.co/TWsy09G/send-location.png) -9. Send Audio ![Send Location](https://i.ibb.co/p1wL4wh/Send-Audio.png) -10. Send Poll ![Send Poll](https://i.ibb.co/mq2fGHz/send-poll.png) -11. Revoke Message ![Revoke Message](https://i.ibb.co/yswhvQY/revoke.png?v1) -12. Reaction Message ![Revoke Message](https://i.ibb.co/BfHgSHG/react-message.png) -13. Edit Message ![Edit Message](https://i.ibb.co/kXfpqJw/update-message.png) -14. User Info ![User Info](https://i.ibb.co/3zjX6Cz/user-info.png?v=1) -15. User Avatar ![User Avatar](https://i.ibb.co/ZmJZ4ZW/search-avatar.png?v=1) -16. My Privacy ![My Privacy](https://i.ibb.co/Cw1sMQz/my-privacy.png) -17. My Group ![My Group](https://i.ibb.co/WB268Xy/list-group.png) -18. Auto Reply ![Auto Reply](https://i.ibb.co/D4rTytX/IMG-20220517-162500.jpg) -19. Basic Auth Prompt ![Basic Auth](https://i.ibb.co/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) +### User Interface + +| Description | Image | +|--------------------|------------------------------------------------------------------------------------------| +| Homepage | ![Homepage](https://i.ibb.co.com/L0B1LVb/homepage-v4-16.png) | +| Login | ![Login](https://i.ibb.co.com/jkcB15R/login.png?v=1) | +| Login With Code | ![Login With Code](https://i.ibb.co.com/rdJGvGw/paircode.png) | +| Send Message | ![Send Message](https://i.ibb.co.com/rc3NXMX/send-message.png?v1) | +| Send Image | ![Send Image](https://i.ibb.co.com/BcFL3SD/send-image.png?v1) | +| Send File | ![Send File](https://i.ibb.co.com/f4yxjpp/send-file.png) | +| Send Video | ![Send Video](https://i.ibb.co.com/PrD3P51/send-video.png) | +| Send Contact | ![Send Contact](https://i.ibb.co.com/4810H7N/send-contact.png) | +| Send Location | ![Send Location](https://i.ibb.co.com/TWsy09G/send-location.png) | +| Send Audio | ![Send Audio](https://i.ibb.co.com/p1wL4wh/Send-Audio.png) | +| Send Poll | ![Send Poll](https://i.ibb.co.com/mq2fGHz/send-poll.png) | +| Revoke Message | ![Revoke Message](https://i.ibb.co.com/yswhvQY/revoke.png?v1) | +| Delete Message | ![Delete Message](https://i.ibb.co.com/F70SZ84/image.png) | +| Reaction Message | ![Reaction Message](https://i.ibb.co.com/BfHgSHG/react-message.png) | +| Edit Message | ![Edit Message](https://i.ibb.co.com/kXfpqJw/update-message.png) | +| User Info | ![User Info](https://i.ibb.co.com/3zjX6Cz/user-info.png?v=1) | +| User Avatar | ![User Avatar](https://i.ibb.co.com/ZmJZ4ZW/search-avatar.png?v=1) | +| My Privacy | ![My Privacy](https://i.ibb.co.com/Cw1sMQz/my-privacy.png) | +| My Group | ![My Group](https://i.ibb.co.com/WB268Xy/list-group.png) | +| Auto Reply | ![Auto Reply](https://i.ibb.co.com/D4rTytX/IMG-20220517-162500.jpg) | +| Basic Auth Prompt | ![Basic Auth Prompt](https://i.ibb.co.com/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) | +| Manage Participant | ![Manage Participant](https://i.ibb.co.com/ynrN7cr/manage-participant.png) | ### Mac OS NOTE diff --git a/src/cmd/root.go b/src/cmd/root.go index cd6e503..d99fc69 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -49,6 +49,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&config.WhatsappAutoReplyMessage, "autoreply", "", config.WhatsappAutoReplyMessage, `auto reply when received message --autoreply | example: --autoreply="Don't reply this message"`) rootCmd.PersistentFlags().StringVarP(&config.WhatsappWebhook, "webhook", "w", config.WhatsappWebhook, `forward event to webhook --webhook | example: --webhook="https://yourcallback.com/callback"`) rootCmd.PersistentFlags().StringVarP(&config.WhatsappWebhookSecret, "webhook-secret", "", config.WhatsappWebhookSecret, `secure webhook request --webhook-secret | example: --webhook-secret="super-secret-key"`) + rootCmd.PersistentFlags().BoolVarP(&config.WhatsappAccountValidation, "account-validation", "", config.WhatsappAccountValidation, `enable or disable account validation --account-validation | example: --account-validation=true`) } func runRest(_ *cobra.Command, _ []string) { diff --git a/src/config/settings.go b/src/config/settings.go index 20cb48f..47b7c42 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -1,15 +1,15 @@ package config import ( - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/proto/waCompanionReg" ) var ( - AppVersion = "v4.13.0" + AppVersion = "v4.17.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" - AppPlatform = waProto.DeviceProps_PlatformType(1) + AppPlatform = waCompanionReg.DeviceProps_PlatformType(1) AppBasicAuthCredential string PathQrCode = "statics/qrcode" @@ -27,4 +27,5 @@ var ( WhatsappSettingMaxVideoSize int64 = 100000000 // 100MB WhatsappTypeUser = "@s.whatsapp.net" WhatsappTypeGroup = "@g.us" + WhatsappAccountValidation = true ) diff --git a/src/domains/app/app.go b/src/domains/app/app.go index 8c00e8f..dbe3bb3 100644 --- a/src/domains/app/app.go +++ b/src/domains/app/app.go @@ -7,6 +7,7 @@ import ( type IAppService interface { Login(ctx context.Context) (response LoginResponse, err error) + LoginWithCode(ctx context.Context, phoneNumber string) (loginCode string, err error) Logout(ctx context.Context) (err error) Reconnect(ctx context.Context) (err error) FirstDevice(ctx context.Context) (response DevicesResponse, err error) diff --git a/src/domains/group/group.go b/src/domains/group/group.go index c89e4ed..59d6764 100644 --- a/src/domains/group/group.go +++ b/src/domains/group/group.go @@ -1,12 +1,15 @@ package group -import "context" +import ( + "context" + "go.mau.fi/whatsmeow" +) type IGroupService interface { JoinGroupWithLink(ctx context.Context, request JoinGroupWithLinkRequest) (groupID string, err error) LeaveGroup(ctx context.Context, request LeaveGroupRequest) (err error) CreateGroup(ctx context.Context, request CreateGroupRequest) (groupID string, err error) - AddParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error) + ManageParticipant(ctx context.Context, request ParticipantRequest) (result []ParticipantStatus, err error) } type JoinGroupWithLinkRequest struct { @@ -23,8 +26,9 @@ type CreateGroupRequest struct { } type ParticipantRequest struct { - GroupID string `json:"group_id" form:"group_id"` - Participants []string `json:"participants" form:"participants"` + GroupID string `json:"group_id" form:"group_id"` + Participants []string `json:"participants" form:"participants"` + Action whatsmeow.ParticipantChange `json:"action" form:"action"` } type ParticipantStatus struct { diff --git a/src/domains/message/message.go b/src/domains/message/message.go index a65a44f..282781f 100644 --- a/src/domains/message/message.go +++ b/src/domains/message/message.go @@ -3,7 +3,35 @@ package message import "context" type IMessageService interface { - ReactMessage(ctx context.Context, request ReactionRequest) (response ReactionResponse, err error) - RevokeMessage(ctx context.Context, request RevokeRequest) (response RevokeResponse, err error) - UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response UpdateMessageResponse, err error) + ReactMessage(ctx context.Context, request ReactionRequest) (response GenericResponse, err error) + RevokeMessage(ctx context.Context, request RevokeRequest) (response GenericResponse, err error) + UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response GenericResponse, err error) + DeleteMessage(ctx context.Context, request DeleteRequest) (err error) +} + +type GenericResponse struct { + MessageID string `json:"message_id"` + Status string `json:"status"` +} + +type RevokeRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Phone string `json:"phone" form:"phone"` +} + +type DeleteRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Phone string `json:"phone" form:"phone"` +} + +type ReactionRequest struct { + MessageID string `json:"message_id" form:"message_id"` + Phone string `json:"phone" form:"phone"` + Emoji string `json:"emoji" form:"emoji"` +} + +type UpdateMessageRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Message string `json:"message" form:"message"` + Phone string `json:"phone" form:"phone"` } diff --git a/src/domains/message/reaction.go b/src/domains/message/reaction.go deleted file mode 100644 index 45a5c8f..0000000 --- a/src/domains/message/reaction.go +++ /dev/null @@ -1,12 +0,0 @@ -package message - -type ReactionRequest struct { - MessageID string `json:"message_id" form:"message_id"` - Phone string `json:"phone" form:"phone"` - Emoji string `json:"emoji" form:"emoji"` -} - -type ReactionResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/domains/message/revoke.go b/src/domains/message/revoke.go deleted file mode 100644 index e7567e9..0000000 --- a/src/domains/message/revoke.go +++ /dev/null @@ -1,11 +0,0 @@ -package message - -type RevokeRequest struct { - MessageID string `json:"message_id" uri:"message_id"` - Phone string `json:"phone" form:"phone"` -} - -type RevokeResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/domains/message/update.go b/src/domains/message/update.go deleted file mode 100644 index 5321055..0000000 --- a/src/domains/message/update.go +++ /dev/null @@ -1,12 +0,0 @@ -package message - -type UpdateMessageRequest struct { - MessageID string `json:"message_id" uri:"message_id"` - Message string `json:"message" form:"message"` - Phone string `json:"phone" form:"phone"` -} - -type UpdateMessageResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/go.mod b/src/go.mod index 977e4a2..4c74d60 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,25 +1,25 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice -go 1.21 +go 1.22 require ( - github.com/PuerkitoBio/goquery v1.9.1 + github.com/PuerkitoBio/goquery v1.9.2 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 - github.com/gofiber/fiber/v2 v2.52.4 - github.com/gofiber/template/html/v2 v2.1.1 + github.com/gofiber/fiber/v2 v2.52.5 + github.com/gofiber/template/html/v2 v2.1.2 github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/sirupsen/logrus v1.9.3 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 - github.com/valyala/fasthttp v1.52.0 - go.mau.fi/libsignal v0.1.0 - go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c - google.golang.org/protobuf v1.33.0 + github.com/valyala/fasthttp v1.55.0 + go.mau.fi/libsignal v0.1.1 + go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 + google.golang.org/protobuf v1.34.2 ) require ( @@ -28,28 +28,28 @@ require ( github.com/andybalholm/cascadia v1.3.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fasthttp/websocket v1.5.8 // indirect + github.com/fasthttp/websocket v1.5.10 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/zerolog v1.32.0 // indirect - github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect + github.com/rs/zerolog v1.33.0 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - go.mau.fi/util v0.4.2 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/image v0.15.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect + go.mau.fi/util v0.7.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/image v0.19.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/src/go.sum b/src/go.sum index 7db8366..c886eb6 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,7 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI= -github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY= +github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= +github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= @@ -10,7 +10,7 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -18,17 +18,17 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8= -github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0= +github.com/fasthttp/websocket v1.5.10 h1:bc7NIGyrg1L6sd5pRzCIbXpro54SZLEluZCu0rOpcN4= +github.com/fasthttp/websocket v1.5.10/go.mod h1:BwHeuXGWzCW1/BIKUKD3+qfCl+cTdsHu/f243NcAI/Q= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= -github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= -github.com/gofiber/template/html/v2 v2.1.1 h1:QEy3O3EBkvwDthy5bXVGUseOyO6ldJoiDxlF4+MJiV8= -github.com/gofiber/template/html/v2 v2.1.1/go.mod h1:2G0GHHOUx70C1LDncoBpe4T6maQbNa4x1CVNFW0wju0= +github.com/gofiber/template/html/v2 v2.1.2 h1:wkK/mYJ3nIhongTkG3t0QgV4ADdgOYJYVSAF2AHnh8Y= +github.com/gofiber/template/html/v2 v2.1.2/go.mod h1:E98Z/FzvpaSib06aWEgYk6GXNf3ctoyaJH8yW5ay5ak= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= @@ -37,12 +37,12 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -54,8 +54,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -65,17 +65,17 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= -github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +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/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= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -85,24 +85,24 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= -github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mau.fi/libsignal v0.1.0 h1:vAKI/nJ5tMhdzke4cTK1fb0idJzz1JuEIpmjprueC+c= -go.mau.fi/libsignal v0.1.0/go.mod h1:R8ovrTezxtUNzCQE5PH30StOQWWeBskBsWE55vMfY9I= -go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30= -go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= -go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c h1:a5O4nqmwUWvmC+27RUdefkuy5XzMOEUqR9ji+/BcHZA= -go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c/go.mod h1:kNI5foyzqd77d5HaWc1Jico6/rxtZ/UE8nr80hIsbIk= +go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI= +go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw= +go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws= +go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0= +go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 h1:Aa4uov0rM0SQQ7Fc/TZZpmQEGksie2SVTv/UuCJwViI= +go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= 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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -110,8 +110,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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= @@ -126,8 +126,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -144,8 +144,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/internal/rest/app.go b/src/internal/rest/app.go index 19775d4..c72cde0 100644 --- a/src/internal/rest/app.go +++ b/src/internal/rest/app.go @@ -14,6 +14,7 @@ type App struct { func InitRestApp(app *fiber.App, service domainApp.IAppService) App { rest := App{Service: service} app.Get("/app/login", rest.Login) + app.Get("/app/login-with-code", rest.LoginWithCode) app.Get("/app/logout", rest.Logout) app.Get("/app/reconnect", rest.Reconnect) app.Get("/app/devices", rest.Devices) @@ -36,6 +37,20 @@ func (handler *App) Login(c *fiber.Ctx) error { }) } +func (handler *App) LoginWithCode(c *fiber.Ctx) error { + pairCode, err := handler.Service.LoginWithCode(c.UserContext(), c.Query("phone")) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Login with code success", + Results: map[string]any{ + "pair_code": pairCode, + }, + }) +} + func (handler *App) Logout(c *fiber.Ctx) error { err := handler.Service.Logout(c.UserContext()) utils.PanicIfNeeded(err) diff --git a/src/internal/rest/group.go b/src/internal/rest/group.go index 95ccef6..53f98bd 100644 --- a/src/internal/rest/group.go +++ b/src/internal/rest/group.go @@ -6,6 +6,7 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/gofiber/fiber/v2" + "go.mau.fi/whatsmeow" ) type Group struct { @@ -18,6 +19,9 @@ func InitRestGroup(app *fiber.App, service domainGroup.IGroupService) Group { app.Post("/group/join-with-link", rest.JoinGroupWithLink) app.Post("/group/leave", rest.LeaveGroup) app.Post("/group/participants", rest.AddParticipants) + app.Post("/group/participants/remove", rest.DeleteParticipants) + app.Post("/group/participants/promote", rest.PromoteParticipants) + app.Post("/group/participants/demote", rest.DemoteParticipants) return rest } @@ -81,7 +85,9 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error { whatsapp.SanitizePhone(&request.GroupID) - result, err := controller.Service.AddParticipant(c.UserContext(), request) + request.Action = whatsmeow.ParticipantChangeAdd + + result, err := controller.Service.ManageParticipant(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ @@ -91,3 +97,63 @@ func (controller *Group) AddParticipants(c *fiber.Ctx) error { Results: result, }) } + +func (controller *Group) DeleteParticipants(c *fiber.Ctx) error { + var request domainGroup.ParticipantRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + whatsapp.SanitizePhone(&request.GroupID) + + request.Action = whatsmeow.ParticipantChangeRemove + + result, err := controller.Service.ManageParticipant(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success delete participants", + Results: result, + }) +} + +func (controller *Group) PromoteParticipants(c *fiber.Ctx) error { + var request domainGroup.ParticipantRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + whatsapp.SanitizePhone(&request.GroupID) + + request.Action = whatsmeow.ParticipantChangePromote + + result, err := controller.Service.ManageParticipant(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success promote participants", + Results: result, + }) +} + +func (controller *Group) DemoteParticipants(c *fiber.Ctx) error { + var request domainGroup.ParticipantRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + whatsapp.SanitizePhone(&request.GroupID) + + request.Action = whatsmeow.ParticipantChangeDemote + + result, err := controller.Service.ManageParticipant(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success demote participants", + Results: result, + }) +} diff --git a/src/internal/rest/message.go b/src/internal/rest/message.go index 71e6329..c14d223 100644 --- a/src/internal/rest/message.go +++ b/src/internal/rest/message.go @@ -16,6 +16,7 @@ func InitRestMessage(app *fiber.App, service domainMessage.IMessageService) Mess rest := Message{Service: service} app.Post("/message/:message_id/reaction", rest.ReactMessage) app.Post("/message/:message_id/revoke", rest.RevokeMessage) + app.Post("/message/:message_id/delete", rest.DeleteMessage) app.Post("/message/:message_id/update", rest.UpdateMessage) return rest } @@ -39,6 +40,25 @@ func (controller *Message) RevokeMessage(c *fiber.Ctx) error { }) } +func (controller *Message) DeleteMessage(c *fiber.Ctx) error { + var request domainMessage.DeleteRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + request.MessageID = c.Params("message_id") + whatsapp.SanitizePhone(&request.Phone) + + err = controller.Service.DeleteMessage(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Message deleted successfully", + Results: nil, + }) +} + func (controller *Message) UpdateMessage(c *fiber.Ctx) error { var request domainMessage.UpdateMessageRequest err := c.BodyParser(&request) diff --git a/src/pkg/error/app_error.go b/src/pkg/error/app_error.go index 79b9f93..5d4c26d 100644 --- a/src/pkg/error/app_error.go +++ b/src/pkg/error/app_error.go @@ -101,7 +101,7 @@ func (err sessionSavedError) StatusCode() int { } var ( - ErrAlreadyLoggedIn = LoginError("you already logged in :)") + ErrAlreadyLoggedIn = LoginError("you are already logged in.") ErrNotConnected = throwAuthError("you are not connect to services server, please reconnect") ErrNotLoggedIn = throwAuthError("you are not logged in") ErrReconnect = throwReconnectError("reconnect error") diff --git a/src/pkg/utils/general.go b/src/pkg/utils/general.go index eddb504..f530c04 100644 --- a/src/pkg/utils/general.go +++ b/src/pkg/utils/general.go @@ -92,3 +92,16 @@ func GetMetaDataFromURL(url string) (meta Metadata) { fmt.Println("Meta data:", meta) return meta } + +// ContainsMention is checking if message contains mention, then return only mention without @ +func ContainsMention(message string) []string { + var mentions []string + words := strings.Fields(message) + for _, word := range words { + if strings.HasPrefix(word, "@") { + mentions = append(mentions, word[1:]) + } + } + + return mentions +} diff --git a/src/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index 956fb8b..a6851bd 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -23,7 +23,7 @@ import ( "github.com/sirupsen/logrus" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/store/sqlstore" "go.mau.fi/whatsmeow/types" @@ -141,7 +141,7 @@ func IsOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { func ValidateJidWithLogin(waCli *whatsmeow.Client, jid string) (types.JID, error) { MustLogin(waCli) - if !IsOnWhatsapp(waCli, jid) { + if config.WhatsappAccountValidation && !IsOnWhatsapp(waCli, jid) { return types.JID{}, pkgError.InvalidJID(fmt.Sprintf("Phone %s is not on whatsapp", jid)) } @@ -192,6 +192,8 @@ func MustLogin(waCli *whatsmeow.Client) { func handler(rawEvt interface{}) { switch evt := rawEvt.(type) { + case *events.DeleteForMe: + log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String()) case *events.AppStateSyncComplete: if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { err := cli.SendPresence(types.PresenceAvailable) @@ -253,7 +255,7 @@ func handler(rawEvt interface{}) { if config.WhatsappAutoReplyMessage != "" && !isGroupJid(evt.Info.Chat.String()) && !strings.Contains(evt.Info.SourceString(), "broadcast") { - _, _ = cli.SendMessage(context.Background(), evt.Info.Sender, &waProto.Message{Conversation: proto.String(config.WhatsappAutoReplyMessage)}) + _, _ = cli.SendMessage(context.Background(), evt.Info.Sender, &waE2E.Message{Conversation: proto.String(config.WhatsappAutoReplyMessage)}) } if config.WhatsappWebhook != "" && @@ -325,7 +327,7 @@ func forwardToWebhook(evt *events.Message) error { message.ID = evt.Info.ID if extendedMessage := evt.Message.ExtendedTextMessage.GetText(); extendedMessage != "" { message.Text = extendedMessage - message.RepliedId = evt.Message.ExtendedTextMessage.ContextInfo.GetStanzaId() + message.RepliedId = evt.Message.ExtendedTextMessage.ContextInfo.GetStanzaID() } var quotedmessage any @@ -343,7 +345,7 @@ func forwardToWebhook(evt *events.Message) error { var waReaction evtReaction if reactionMessage := evt.Message.ReactionMessage; reactionMessage != nil { waReaction.Message = reactionMessage.GetText() - waReaction.ID = reactionMessage.GetKey().GetId() + waReaction.ID = reactionMessage.GetKey().GetID() } body := map[string]interface{}{ @@ -469,23 +471,29 @@ func ExtractMedia(storageLocation string, mediaFile whatsmeow.DownloadableMessag } switch media := mediaFile.(type) { - case *waProto.ImageMessage: + case *waE2E.ImageMessage: extractedMedia.MimeType = media.GetMimetype() extractedMedia.Caption = media.GetCaption() - case *waProto.AudioMessage: + case *waE2E.AudioMessage: extractedMedia.MimeType = media.GetMimetype() - case *waProto.VideoMessage: + case *waE2E.VideoMessage: extractedMedia.MimeType = media.GetMimetype() extractedMedia.Caption = media.GetCaption() - case *waProto.StickerMessage: + case *waE2E.StickerMessage: extractedMedia.MimeType = media.GetMimetype() - case *waProto.DocumentMessage: + case *waE2E.DocumentMessage: extractedMedia.MimeType = media.GetMimetype() extractedMedia.Caption = media.GetCaption() } - extensions, _ := mime.ExtensionsByType(extractedMedia.MimeType) - extractedMedia.MediaPath = fmt.Sprintf("%s/%d-%s%s", storageLocation, time.Now().Unix(), uuid.NewString(), extensions[0]) + var extension string + if ext, err := mime.ExtensionsByType(extractedMedia.MimeType); err != nil && len(ext) > 0 { + extension = ext[0] + } else if parts := strings.Split(extractedMedia.MimeType, "/"); len(parts) > 1 { + extension = "." + parts[len(parts)-1] + } + + extractedMedia.MediaPath = fmt.Sprintf("%s/%d-%s%s", storageLocation, time.Now().Unix(), uuid.NewString(), extension) err = os.WriteFile(extractedMedia.MediaPath, data, 0600) if err != nil { return extractedMedia, err diff --git a/src/services/app.go b/src/services/app.go index 9cf9d40..8aca9b9 100644 --- a/src/services/app.go +++ b/src/services/app.go @@ -7,6 +7,7 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" fiberUtils "github.com/gofiber/fiber/v2/utils" "github.com/sirupsen/logrus" "github.com/skip2/go-qrcode" @@ -90,6 +91,28 @@ func (service serviceApp) Login(_ context.Context) (response domainApp.LoginResp return response, nil } +func (service serviceApp) LoginWithCode(ctx context.Context, phoneNumber string) (loginCode string, err error) { + if err = validations.ValidateLoginWithCode(ctx, phoneNumber); err != nil { + logrus.Errorf("Error when validate login with code: %s", err.Error()) + return loginCode, err + } + + // detect is already logged in + if service.WaCli.IsLoggedIn() { + logrus.Warn("User is already logged in") + return loginCode, pkgError.ErrAlreadyLoggedIn + } + + loginCode, err = service.WaCli.PairPhone(phoneNumber, true, whatsmeow.PairClientChrome, "Chrome (Linux)") + if err != nil { + logrus.Errorf("Error when pairing phone: %s", err.Error()) + return loginCode, err + } + + logrus.Infof("Successfully paired phone with code: %s", loginCode) + return loginCode, nil +} + func (service serviceApp) Logout(_ context.Context) (err error) { // delete history files, err := filepath.Glob(fmt.Sprintf("./%s/history-*", config.PathStorages)) diff --git a/src/services/group.go b/src/services/group.go index 3e096cb..afe2aa1 100644 --- a/src/services/group.go +++ b/src/services/group.go @@ -73,7 +73,7 @@ func (service groupService) CreateGroup(ctx context.Context, request domainGroup return groupInfo.JID.String(), nil } -func (service groupService) AddParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) { +func (service groupService) ManageParticipant(ctx context.Context, request domainGroup.ParticipantRequest) (result []domainGroup.ParticipantStatus, err error) { if err = validations.ValidateParticipant(ctx, request); err != nil { return result, err } @@ -89,7 +89,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr return result, err } - participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, whatsmeow.ParticipantChangeAdd) + participants, err := service.WaCli.UpdateGroupParticipants(groupJID, participantsJID, request.Action) if err != nil { return result, err } @@ -105,7 +105,7 @@ func (service groupService) AddParticipant(ctx context.Context, request domainGr result = append(result, domainGroup.ParticipantStatus{ Participant: participant.JID.String(), Status: "success", - Message: "Participant added", + Message: "Action success", }) } } diff --git a/src/services/message.go b/src/services/message.go index 9ba678a..9717e23 100644 --- a/src/services/message.go +++ b/src/services/message.go @@ -8,7 +8,10 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "go.mau.fi/whatsmeow" - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waCommon" + "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/proto/waSyncAction" "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" "time" @@ -24,7 +27,7 @@ func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageService { } } -func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.ReactionResponse, err error) { +func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.GenericResponse, err error) { if err = validations.ValidateReactMessage(ctx, request); err != nil { return response, err } @@ -33,15 +36,15 @@ func (service serviceMessage) ReactMessage(ctx context.Context, request message. return response, err } - msg := &waProto.Message{ - ReactionMessage: &waProto.ReactionMessage{ - Key: &waProto.MessageKey{ + msg := &waE2E.Message{ + ReactionMessage: &waE2E.ReactionMessage{ + Key: &waCommon.MessageKey{ FromMe: proto.Bool(true), - Id: proto.String(request.MessageID), - RemoteJid: proto.String(dataWaRecipient.String()), + ID: proto.String(request.MessageID), + RemoteJID: proto.String(dataWaRecipient.String()), }, Text: proto.String(request.Emoji), - SenderTimestampMs: proto.Int64(time.Now().UnixMilli()), + SenderTimestampMS: proto.Int64(time.Now().UnixMilli()), }, } ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) @@ -54,7 +57,7 @@ func (service serviceMessage) ReactMessage(ctx context.Context, request message. return response, nil } -func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.RevokeResponse, err error) { +func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateRevokeMessage(ctx, request); err != nil { return response, err } @@ -73,7 +76,41 @@ func (service serviceMessage) RevokeMessage(ctx context.Context, request domainM return response, nil } -func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.UpdateMessageResponse, err error) { +func (service serviceMessage) DeleteMessage(ctx context.Context, request domainMessage.DeleteRequest) (err error) { + if err = validations.ValidateDeleteMessage(ctx, request); err != nil { + return err + } + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return err + } + + isFromMe := "1" + if len(request.MessageID) > 22 { + isFromMe = "0" + } + + patchInfo := appstate.PatchInfo{ + Timestamp: time.Now(), + Type: appstate.WAPatchRegularHigh, + Mutations: []appstate.MutationInfo{{ + Index: []string{appstate.IndexDeleteMessageForMe, dataWaRecipient.String(), request.MessageID, isFromMe, service.WaCli.Store.ID.String()}, + Value: &waSyncAction.SyncActionValue{ + DeleteMessageForMeAction: &waSyncAction.DeleteMessageForMeAction{ + DeleteMedia: proto.Bool(true), + MessageTimestamp: proto.Int64(time.Now().UnixMilli()), + }, + }, + }}, + } + + if err = service.WaCli.SendAppState(patchInfo); err != nil { + return err + } + return nil +} + +func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateUpdateMessage(ctx, request); err != nil { return response, err } @@ -83,7 +120,7 @@ func (service serviceMessage) UpdateMessage(ctx context.Context, request domainM return response, err } - msg := &waProto.Message{Conversation: proto.String(request.Message)} + msg := &waE2E.Message{Conversation: proto.String(request.Message)} ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, service.WaCli.BuildEdit(dataWaRecipient, request.MessageID, msg)) if err != nil { return response, err diff --git a/src/services/send.go b/src/services/send.go index f5b1b6e..08fb2f8 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" "go.mau.fi/whatsmeow" - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/proto/waE2E" "google.golang.org/protobuf/proto" "net/http" "os" @@ -46,7 +46,18 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess } // Send message - msg := &waProto.Message{Conversation: proto.String(request.Message)} + msg := &waE2E.Message{ + ExtendedTextMessage: &waE2E.ExtendedTextMessage{ + Text: proto.String(request.Message), + }, + } + + parsedMentions := service.getMentionFromText(ctx, request.Message) + if len(parsedMentions) > 0 { + msg.ExtendedTextMessage.ContextInfo = &waE2E.ContextInfo{ + MentionedJID: parsedMentions, + } + } // Reply message if request.ReplyMessageID != nil && *request.ReplyMessageID != "" { @@ -59,18 +70,20 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess participantJID = firstDevice.Device } - msg = &waProto.Message{ - ExtendedTextMessage: &waProto.ExtendedTextMessage{ - Text: proto.String(request.Message), - ContextInfo: &waProto.ContextInfo{ - StanzaId: request.ReplyMessageID, - Participant: proto.String(participantJID), - QuotedMessage: &waProto.Message{ - Conversation: proto.String(request.Message), - }, + msg.ExtendedTextMessage = &waE2E.ExtendedTextMessage{ + Text: proto.String(request.Message), + ContextInfo: &waE2E.ContextInfo{ + StanzaID: request.ReplyMessageID, + Participant: proto.String(participantJID), + QuotedMessage: &waE2E.Message{ + Conversation: proto.String(request.Message), }, }, } + + if len(parsedMentions) > 0 { + msg.ExtendedTextMessage.ContextInfo.MentionedJID = parsedMentions + } } ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) @@ -154,15 +167,15 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima return response, pkgError.InternalServerError(fmt.Sprintf("failed to read thumbnail %v", err)) } - msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{ - JpegThumbnail: dataWaThumbnail, + msg := &waE2E.Message{ImageMessage: &waE2E.ImageMessage{ + JPEGThumbnail: dataWaThumbnail, Caption: proto.String(dataWaCaption), - Url: proto.String(uploadedImage.URL), + URL: proto.String(uploadedImage.URL), DirectPath: proto.String(uploadedImage.DirectPath), MediaKey: uploadedImage.MediaKey, Mimetype: proto.String(http.DetectContentType(dataWaImage)), - FileEncSha256: uploadedImage.FileEncSHA256, - FileSha256: uploadedImage.FileSHA256, + FileEncSHA256: uploadedImage.FileEncSHA256, + FileSHA256: uploadedImage.FileSHA256, FileLength: proto.Uint64(uint64(len(dataWaImage))), ViewOnce: proto.Bool(request.ViewOnce), }} @@ -196,24 +209,21 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File fileMimeType := http.DetectContentType(fileBytes) // Send to WA server - if err != nil { - return response, err - } uploadedFile, err := service.WaCli.Upload(context.Background(), fileBytes, whatsmeow.MediaDocument) if err != nil { fmt.Printf("Failed to upload file: %v", err) return response, err } - msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{ - Url: proto.String(uploadedFile.URL), + msg := &waE2E.Message{DocumentMessage: &waE2E.DocumentMessage{ + URL: proto.String(uploadedFile.URL), Mimetype: proto.String(fileMimeType), Title: proto.String(request.File.Filename), - FileSha256: uploadedFile.FileSHA256, + FileSHA256: uploadedFile.FileSHA256, FileLength: proto.Uint64(uploadedFile.FileLength), MediaKey: uploadedFile.MediaKey, FileName: proto.String(request.File.Filename), - FileEncSha256: uploadedFile.FileEncSHA256, + FileEncSHA256: uploadedFile.FileEncSHA256, DirectPath: proto.String(uploadedFile.DirectPath), Caption: proto.String(request.Caption), }} @@ -310,19 +320,19 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid return response, err } - msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ - Url: proto.String(uploaded.URL), + msg := &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ + URL: proto.String(uploaded.URL), Mimetype: proto.String(http.DetectContentType(dataWaVideo)), Caption: proto.String(request.Caption), FileLength: proto.Uint64(uploaded.FileLength), - FileSha256: uploaded.FileSHA256, - FileEncSha256: uploaded.FileEncSHA256, + FileSHA256: uploaded.FileSHA256, + FileEncSHA256: uploaded.FileEncSHA256, MediaKey: uploaded.MediaKey, DirectPath: proto.String(uploaded.DirectPath), ViewOnce: proto.Bool(request.ViewOnce), - JpegThumbnail: dataWaThumbnail, - ThumbnailEncSha256: dataWaThumbnail, - ThumbnailSha256: dataWaThumbnail, + JPEGThumbnail: dataWaThumbnail, + ThumbnailEncSHA256: dataWaThumbnail, + ThumbnailSHA256: dataWaThumbnail, ThumbnailDirectPath: proto.String(uploaded.DirectPath), }} ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) @@ -353,7 +363,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD", request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone) - msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{ + msg := &waE2E.Message{ContactMessage: &waE2E.ContactMessage{ DisplayName: proto.String(request.ContactName), Vcard: proto.String(msgVCard), }} @@ -379,10 +389,10 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link getMetaDataFromURL := utils.GetMetaDataFromURL(request.Link) - msg := &waProto.Message{ExtendedTextMessage: &waProto.ExtendedTextMessage{ + msg := &waE2E.Message{ExtendedTextMessage: &waE2E.ExtendedTextMessage{ Text: proto.String(fmt.Sprintf("%s\n%s", request.Caption, request.Link)), Title: proto.String(getMetaDataFromURL.Title), - CanonicalUrl: proto.String(request.Link), + CanonicalURL: proto.String(request.Link), MatchedText: proto.String(request.Link), Description: proto.String(getMetaDataFromURL.Description), }} @@ -407,8 +417,8 @@ func (service serviceSend) SendLocation(ctx context.Context, request domainSend. } // Compose WhatsApp Proto - msg := &waProto.Message{ - LocationMessage: &waProto.LocationMessage{ + msg := &waE2E.Message{ + LocationMessage: &waE2E.LocationMessage{ DegreesLatitude: proto.Float64(utils.StrToFloat64(request.Latitude)), DegreesLongitude: proto.Float64(utils.StrToFloat64(request.Longitude)), }, @@ -444,14 +454,14 @@ func (service serviceSend) SendAudio(ctx context.Context, request domainSend.Aud return response, err } - msg := &waProto.Message{ - AudioMessage: &waProto.AudioMessage{ - Url: proto.String(audioUploaded.URL), + msg := &waE2E.Message{ + AudioMessage: &waE2E.AudioMessage{ + URL: proto.String(audioUploaded.URL), DirectPath: proto.String(audioUploaded.DirectPath), Mimetype: proto.String(audioMimeType), FileLength: proto.Uint64(audioUploaded.FileLength), - FileSha256: audioUploaded.FileSHA256, - FileEncSha256: audioUploaded.FileEncSHA256, + FileSHA256: audioUploaded.FileSHA256, + FileEncSHA256: audioUploaded.FileEncSHA256, MediaKey: audioUploaded.MediaKey, }, } @@ -485,3 +495,15 @@ func (service serviceSend) SendPoll(ctx context.Context, request domainSend.Poll response.Status = fmt.Sprintf("Send poll success %s (server timestamp: %s)", request.Phone, ts.Timestamp.String()) return response, nil } + +func (service serviceSend) getMentionFromText(ctx context.Context, messages string) (result []string) { + mentions := utils.ContainsMention(messages) + for _, mention := range mentions { + // Get JID from phone number + if dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, mention); err == nil { + result = append(result, dataWaRecipient.String()) + } + } + return result + +} diff --git a/src/validations/app_validation.go b/src/validations/app_validation.go new file mode 100644 index 0000000..e9eb1f7 --- /dev/null +++ b/src/validations/app_validation.go @@ -0,0 +1,21 @@ +package validations + +import ( + "context" + "fmt" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + validation "github.com/go-ozzo/ozzo-validation/v4" + "regexp" +) + +func ValidateLoginWithCode(ctx context.Context, phoneNumber string) error { + // Combine validations using a single ValidateWithContext call + err := validation.ValidateWithContext(ctx, &phoneNumber, + validation.Required, + validation.Match(regexp.MustCompile(`^\+?[0-9]{1,15}$`)), + ) + if err != nil { + return pkgError.ValidationError(fmt.Sprintf("phone_number(%s): %s", phoneNumber, err.Error())) + } + return nil +} diff --git a/src/validations/app_validation_test.go b/src/validations/app_validation_test.go new file mode 100644 index 0000000..5757b32 --- /dev/null +++ b/src/validations/app_validation_test.go @@ -0,0 +1,61 @@ +package validations + +import ( + "context" + "testing" +) + +func TestValidateLoginWithCode(t *testing.T) { + type args struct { + phoneNumber string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Phone with +", + args: args{phoneNumber: "+6281234567890"}, + wantErr: false, + }, + { + name: "Phone without +", + args: args{phoneNumber: "621234567890"}, + wantErr: false, + }, + { + name: "Phone with 0", + args: args{phoneNumber: "081234567890"}, + wantErr: false, + }, + { + name: "Phone contains alphabet", + args: args{phoneNumber: "+6281234567890a"}, + wantErr: true, + }, + { + name: "Empty phone number", + args: args{phoneNumber: ""}, + wantErr: true, + }, + { + name: "Phone with special characters", + args: args{phoneNumber: "+6281234567890!@#"}, + wantErr: true, + }, + { + name: "Extremely long phone number", + args: args{phoneNumber: "+62812345678901234567890"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ValidateLoginWithCode(context.Background(), tt.args.phoneNumber); (err != nil) != tt.wantErr { + t.Errorf("ValidateLoginWithCode() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/src/validations/message_validation.go b/src/validations/message_validation.go index b7caabe..6e268fd 100644 --- a/src/validations/message_validation.go +++ b/src/validations/message_validation.go @@ -48,3 +48,16 @@ func ValidateReactMessage(ctx context.Context, request message.ReactionRequest) return nil } + +func ValidateDeleteMessage(ctx context.Context, request domainMessage.DeleteRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.Phone, validation.Required), + validation.Field(&request.MessageID, validation.Required), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil +} diff --git a/src/views/components/AccountUserInfo.js b/src/views/components/AccountUserInfo.js index 4bc2aa0..a034990 100644 --- a/src/views/components/AccountUserInfo.js +++ b/src/views/components/AccountUserInfo.js @@ -95,7 +95,7 @@ export default {
    -
  1. Nama: {{ name }}
  2. +
  3. Name: {{ name }}
  4. Status: {{ status }}
  5. Device:
      diff --git a/src/views/components/AppLogin.js b/src/views/components/AppLogin.js index b276e7f..20cd062 100644 --- a/src/views/components/AppLogin.js +++ b/src/views/components/AppLogin.js @@ -1,5 +1,8 @@ export default { name: 'AppLogin', + props: { + connected: null, + }, data() { return { login_link: '', @@ -9,6 +12,8 @@ export default { methods: { async openModal() { try { + if (this.connected) throw Error('You are already logged in.'); + await this.submitApi(); $('#modalLogin').modal({ onApprove: function () { @@ -38,7 +43,7 @@ export default {
      Login
      - Scan your QRCode and you can use all this API feature + Scan your QR code to access all API capabilities.
diff --git a/src/views/components/AppLoginWithCode.js b/src/views/components/AppLoginWithCode.js new file mode 100644 index 0000000..8421e02 --- /dev/null +++ b/src/views/components/AppLoginWithCode.js @@ -0,0 +1,109 @@ +export default { + name: 'AppLoginWithCode', + props: { + connected: { + type: Boolean, + default: false, + } + }, + watch: { + connected: function(val) { + if (val) { + // reset form + this.phone = ''; + this.pair_code = null; + + $('#modalLoginWithCode').modal('hide'); + } + }, + }, + data: () => { + return { + phone: '', + submitting: false, + pair_code: null, + }; + }, + methods: { + async openModal() { + try { + if (this.connected) throw Error('You are already logged in.'); + + $('#modalLoginWithCode').modal({ + onApprove: function() { + return false; + }, + }).modal('show'); + } catch (err) { + showErrorInfo(err); + } + }, + async handleSubmit() { + if (this.submitting) return; + try { + this.submitting = true; + const { data } = await http.get(`/app/login-with-code`, { + params: { + phone: this.phone, + }, + }); + this.pair_code = data.results.pair_code; + } catch (err) { + if (err.response) { + showErrorInfo(err.response.data.message); + }else{ + showErrorInfo(err.message); + } + } finally { + this.submitting = false; + } + }, + }, + template: ` +
+
+
Login with Code
+
+ Enter your pairing code to log in and access your devices. +
+
+
+ + + + `, +}; \ No newline at end of file diff --git a/src/views/components/AppReconnect.js b/src/views/components/AppReconnect.js index dcc507b..1d41451 100644 --- a/src/views/components/AppReconnect.js +++ b/src/views/components/AppReconnect.js @@ -28,8 +28,7 @@ export default {
Reconnect
- Reconnect to whatsapp server, please do this if your api doesn't work or your application is down or - restart + Please reconnect to the WhatsApp service if your API doesn't work or if your app is down.
diff --git a/src/views/components/GroupAddParticipants.js b/src/views/components/GroupManageParticipants.js similarity index 65% rename from src/views/components/GroupAddParticipants.js rename to src/views/components/GroupManageParticipants.js index 4d272fc..914114b 100644 --- a/src/views/components/GroupAddParticipants.js +++ b/src/views/components/GroupManageParticipants.js @@ -1,48 +1,66 @@ export default { - name: 'AddParticipantsToGroup', + name: 'ManageGroupParticipants', data() { return { loading: false, group: '', + action: 'add', // add, remove, promote, demote participants: ['', ''], - } + }; }, computed: { group_id() { - return `${this.group}@${window.TYPEGROUP}` - } + return `${this.group}@${window.TYPEGROUP}`; + }, }, methods: { openModal() { $('#modalGroupAddParticipant').modal({ - onApprove: function () { + onApprove: function() { return false; - } + }, }).modal('show'); }, handleAddParticipant() { - this.participants.push('') + this.participants.push(''); }, handleDeleteParticipant(index) { - this.participants.splice(index, 1) + this.participants.splice(index, 1); }, async handleSubmit() { try { - let response = await this.submitApi() - showSuccessInfo(response) + let response = await this.submitApi(); + showSuccessInfo(response); $('#modalGroupAddParticipant').modal('hide'); } catch (err) { - showErrorInfo(err) + showErrorInfo(err); } }, async submitApi() { this.loading = true; try { - let response = await window.http.post(`/group/participants`, { + const payload = { group_id: this.group_id, // convert participant become list of string - participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`) - }) + participants: this.participants.filter(participant => participant !== '').map(participant => `${participant}`), + }; + + let response; + switch (this.action) { + case 'add': + response = await window.http.post(`/group/participants`, payload); + break; + case 'remove': + response = await window.http.post(`/group/participants/remove`, payload); + break; + case 'promote': + response = await window.http.post(`/group/participants/promote`, payload); + break; + case 'demote': + response = await window.http.post(`/group/participants/demote`, payload); + break; + } + this.handleReset(); return response.data.message; } catch (error) { @@ -56,6 +74,7 @@ export default { }, handleReset() { this.group = ''; + this.action = 'add'; this.participants = ['', '']; }, }, @@ -63,9 +82,9 @@ export default {
Group -
Add Participants
+
Manage Participants
- Add multiple participants + Add/Remove/Promote/Demote Participants
@@ -74,7 +93,7 @@ export default { + +
+ + +
- Create + Submit
- ` -} \ No newline at end of file + `, +}; \ No newline at end of file diff --git a/src/views/components/MessageDelete.js b/src/views/components/MessageDelete.js new file mode 100644 index 0000000..cb6f46d --- /dev/null +++ b/src/views/components/MessageDelete.js @@ -0,0 +1,106 @@ +export default { + name: 'DeleteMessage', + data() { + return { + type: 'user', + phone: '', + message_id: '', + loading: false, + } + }, + computed: { + phone_id() { + return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}` + } + }, + methods: { + openModal() { + $('#modalMessageDelete').modal({ + onApprove: function () { + return false; + } + }).modal('show'); + }, + async handleSubmit() { + try { + let response = await this.submitApi() + showSuccessInfo(response) + $('#modalMessageDelete').modal('hide'); + } catch (err) { + showErrorInfo(err) + } + }, + async submitApi() { + this.loading = true; + try { + const payload = {phone: this.phone_id, message: this.new_message} + + let response = await window.http.post(`/message/${this.message_id}/delete`, payload) + this.handleReset(); + return response.data.message; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } finally { + this.loading = false; + } + }, + handleReset() { + this.type = 'user'; + this.phone = ''; + this.message_id = ''; + this.new_message = ''; + this.loading = false; + }, + }, + template: ` +
+
+ Message +
Delete Message
+
+ Delete your sent message +
+
+
+ + + + ` +} \ No newline at end of file diff --git a/src/views/index.html b/src/views/index.html index 75723e0..8f3c029 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -41,9 +41,10 @@
- + +
@@ -68,6 +69,7 @@
+ @@ -126,6 +128,7 @@