Browse Source

update-ver

pull/159/head
Abdul Rahman 2 years ago
parent
commit
1d063e8369
  1. 12
      .github/workflows/build-docker-image.yaml
  2. 4
      .github/workflows/release-linux.yml
  3. 2
      .github/workflows/release-mac.yml
  4. 2
      .github/workflows/release-windows.yml
  5. 3
      .gitignore
  6. 6
      docker/golang.Dockerfile
  7. 169
      docs/openapi.yaml
  8. 75
      readme.md
  9. 12
      src/cmd/root.go
  10. 20
      src/config/settings.go
  11. 1
      src/domains/app/app.go
  12. 12
      src/domains/group/group.go
  13. 36
      src/go.mod
  14. 90
      src/go.sum
  15. 15
      src/internal/rest/app.go
  16. 68
      src/internal/rest/group.go
  17. 2
      src/pkg/error/app_error.go
  18. 13
      src/pkg/utils/general.go
  19. 28
      src/pkg/whatsapp/whatsapp.go
  20. 23
      src/services/app.go
  21. 6
      src/services/group.go
  22. 48
      src/services/send.go
  23. 21
      src/validations/app_validation.go
  24. 61
      src/validations/app_validation_test.go
  25. 7
      src/views/components/AppLogin.js
  26. 109
      src/views/components/AppLoginWithCode.js
  27. 3
      src/views/components/AppReconnect.js
  28. 69
      src/views/components/GroupManageParticipants.js
  29. 8
      src/views/index.html

12
.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
${{ 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

4
.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

2
.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

2
.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

3
.gitignore

@ -7,5 +7,4 @@ main
main.exe
*.jpe
src/pkged.go
storages
.env
storages

6
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,12 +14,10 @@ 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.
COPY --from=builder /app/whatsapp /app/whatsapp
# Copy the .env file
COPY .env ./.env
# Run the binary.
ENTRYPOINT ["/app/whatsapp"]

169
docs/openapi.yaml

@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: WhatsApp API MultiDevice
version: 4.0.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
@ -838,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/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/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/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:
@ -960,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:
@ -1153,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:

75
readme.md

@ -33,7 +33,6 @@ Now that we support ARM64 for Linux:
- Webhook for received message
- `--webhook="http://yourwebhook.site/handler"`, or you can simplify
- `-w="http://yourwebhook.site/handler"`
- *Note: Webhook can also be set via environment variable `WhatsappWebhook`*
- For more command `./main --help`
### Required (without docker)
@ -99,21 +98,22 @@ You can fork or edit this source code !
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 |
| Feature | Menu | Method | URL |
|---------|------------------------------|--------|-------------------------------|
| ✅ | Login | GET | /app/login |
| ✅ | Logout | GET | /app/logout |
| ✅ | Reconnect | GET | /app/reconnect |
| ✅ | Devices | GET | /app/devices |
| ✅ | 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 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 |
@ -122,41 +122,46 @@ You can fork or edit this source code !
| ✅ | 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 | DELETE | /group/participants |
| | Promote Participant in Group | POST | /group/participants/promote |
| | Demote Participant in Group | POST | /group/participants/demote |
| ✅ | 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.com/681JTHK/image.png)
2. Login ![Login](https://i.ibb.co.com/jkcB15R/login.png?v=1)
3. Send Message ![Send Message](https://i.ibb.co.com/rc3NXMX/send-message.png?v1)
4. Send Image ![Send Image](https://i.ibb.co.com/BcFL3SD/send-image.png?v1)
5. Send File ![Send File](https://i.ibb.co.com/f4yxjpp/send-file.png)
6. Send Video ![Send Video](https://i.ibb.co.com/PrD3P51/send-video.png)
7. Send Contact ![Send Contact](https://i.ibb.co.com/4810H7N/send-contact.png)
8. Send Location ![Send Location](https://i.ibb.co.com/TWsy09G/send-location.png)
9. Send Audio ![Send Location](https://i.ibb.co.com/p1wL4wh/Send-Audio.png)
10. Send Poll ![Send Poll](https://i.ibb.co.com/mq2fGHz/send-poll.png)
11. Revoke Message ![Revoke Message](https://i.ibb.co.com/yswhvQY/revoke.png?v1)
12. Delete Message ![Delete Message](https://i.ibb.co.com/F70SZ84/image.png)
13. Reaction Message ![Revoke Message](https://i.ibb.co.com/BfHgSHG/react-message.png)
14. Edit Message ![Edit Message](https://i.ibb.co.com/kXfpqJw/update-message.png)
15. User Info ![User Info](https://i.ibb.co.com/3zjX6Cz/user-info.png?v=1)
16. User Avatar ![User Avatar](https://i.ibb.co.com/ZmJZ4ZW/search-avatar.png?v=1)
17. My Privacy ![My Privacy](https://i.ibb.co.com/Cw1sMQz/my-privacy.png)
18. My Group ![My Group](https://i.ibb.co.com/WB268Xy/list-group.png)
19. Auto Reply ![Auto Reply](https://i.ibb.co.com/D4rTytX/IMG-20220517-162500.jpg)
20. Basic Auth Prompt ![Basic Auth](https://i.ibb.co.com/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

12
src/cmd/root.go

@ -13,6 +13,8 @@ import (
"github.com/aldinokemal/go-whatsapp-web-multidevice/services"
"github.com/dustin/go-humanize"
"github.com/gofiber/fiber/v2"
// terminal: "go get github.com/joho/godotenv"
"github.com/joho/godotenv"
"github.com/gofiber/fiber/v2/middleware/basicauth"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/filesystem"
@ -34,7 +36,7 @@ var (
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Short: "Send free whatsapp API",
Long: `This application is from clone https://github.com/aldinokemal/go-whatsapp-web-multidevice,
Long: `This application is from clone https://github.com/aldinokemal/go-whatsapp-web-multidevice,
you can send whatsapp over http api but your whatsapp account have to be multi device version`,
Run: runRest,
}
@ -47,6 +49,14 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&config.AppBasicAuthCredential, "basic-auth", "b", config.AppBasicAuthCredential, "basic auth credential | -b=yourUsername:yourPassword")
rootCmd.PersistentFlags().StringVarP(&config.WhatsappAutoReplyMessage, "autoreply", "", config.WhatsappAutoReplyMessage, `auto reply when received message --autoreply <string> | example: --autoreply="Don't reply this message"`)
rootCmd.PersistentFlags().StringVarP(&config.WhatsappWebhook, "webhook", "w", config.WhatsappWebhook, `forward event to webhook --webhook <string> | example: --webhook="https://yourcallback.com/callback"`)
if config.WhatsappWebhook == "" {
err := godotenv.Load()
if err != nil {
log.Printf("Warning: .env file not loaded. %v", err)
}
config.WhatsappWebhook = os.Getenv("WhatsappWebhook")
}
}
func runRest(_ *cobra.Command, _ []string) {

20
src/config/settings.go

@ -1,18 +1,15 @@
package config
import (
"log"
"os"
"github.com/joho/godotenv"
waProto "go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/proto/waCompanionReg"
)
var (
AppVersion = "v4.14.0"
AppVersion = "v4.16.0"
AppPort = "3000"
AppDebug = false
AppOs = "AldinoKemal"
AppPlatform = waProto.DeviceProps_PlatformType(1)
AppPlatform = waCompanionReg.DeviceProps_PlatformType(1)
AppBasicAuthCredential string
PathQrCode = "statics/qrcode"
@ -30,14 +27,3 @@ var (
WhatsappTypeUser = "@s.whatsapp.net"
WhatsappTypeGroup = "@g.us"
)
func init() {
err := godotenv.Load()
if err != nil {
log.Printf("Warning: .env file not loaded. %v", err)
}
WhatsappWebhook = os.Getenv("WhatsappWebhook")
if WhatsappWebhook == "" {
log.Printf("Warning: WhatsappWebhook environment variable is not set")
}
}

1
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)

12
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 {

36
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.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.54.0
go.mau.fi/libsignal v0.1.0
go.mau.fi/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1
google.golang.org/protobuf v1.34.1
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,29 +28,29 @@ 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.9 // 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/joho/godotenv v1.5.1 // 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.33.0 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // 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.23.0 // indirect
golang.org/x/image v0.16.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.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

90
src/go.sum

@ -1,7 +1,5 @@
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=
@ -12,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=
@ -20,19 +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.9 h1:9deGuzYcCRKjk940kNwSN6Hd14hk4zYwropm4UsUIUQ=
github.com/fasthttp/websocket v1.5.9/go.mod h1:NLzHBFur260OMuZHohOfYQwMTpR7sfSpUnuqKxMpgKA=
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=
@ -41,14 +37,14 @@ 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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=
@ -60,8 +56,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=
@ -71,19 +67,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=
@ -93,32 +87,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.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0=
github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM=
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/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1 h1:mUEEmZs1xk5QHKXjDxiAP4bYgyj8r7PaZCafHN+KMQg=
go.mau.fi/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM=
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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
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.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
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=
@ -126,10 +112,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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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=
@ -144,10 +128,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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.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=
@ -164,10 +146,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.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/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=

15
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)

68
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,
})
}

2
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")

13
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
}

28
src/pkg/whatsapp/whatsapp.go

@ -12,7 +12,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"
@ -251,7 +251,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 != "" &&
@ -314,7 +314,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
@ -332,7 +332,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{}{
@ -443,23 +443,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

23
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))

6
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",
})
}
}

48
src/services/send.go

@ -46,7 +46,18 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess
}
// Send message
msg := &waE2E.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 = &waE2E.Message{
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),
},
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)
@ -196,9 +209,6 @@ 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)
@ -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
}

21
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
}

61
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)
}
})
}
}

7
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 {
<div class="content">
<div class="header">Login</div>
<div class="description">
Scan your QRCode and you can use all this API feature
Scan your QR code to access all API capabilities.
</div>
</div>
</div>

109
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: `
<div class="green card" @click="openModal" style="cursor: pointer">
<div class="content">
<div class="header">Login with Code</div>
<div class="description">
Enter your pairing code to log in and access your devices.
</div>
</div>
</div>
<!-- Modal Login -->
<div class="ui small modal" id="modalLoginWithCode">
<i class="close icon"></i>
<div class="header">
Getting Pair Code
</div>
<div class="content">
<div class="ui message info">
<div class="header">How to pair?</div>
<ol>
<li>Open your Whatsapp</li>
<li>Link a device</li>
<li>Link with pair code</li>
</ol>
</div>
<div class="ui form">
<div class="field">
<label>Phone</label>
<input type="text" v-model="phone" placeholder="Type your phone number"
@keyup.enter="handleSubmit" :disabled="submitting">
<small>Enter to submit</small>
</div>
</div>
<div class="ui grid" v-if="pair_code">
<div class="ui two column centered grid">
<div class="column center aligned">
<div class="header">Pair Code</div>
<p style="font-size: 32px">{{ pair_code }}</p>
</div>
</div>
</div>
</div>
</div>
`,
};

3
src/views/components/AppReconnect.js

@ -28,8 +28,7 @@ export default {
<div class="content">
<div class="header">Reconnect</div>
<div class="description">
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.
</div>
</div>
</div>

69
src/views/components/GroupAddParticipants.js → 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 {
<div class="green card" @click="openModal" style="cursor: pointer">
<div class="content">
<a class="ui green right ribbon label">Group</a>
<div class="header">Add Participants</div>
<div class="header">Manage Participants</div>
<div class="description">
Add multiple participants
Add/Remove/Promote/Demote Participants
</div>
</div>
</div>
@ -74,7 +93,7 @@ export default {
<div class="ui small modal" id="modalGroupAddParticipant">
<i class="close icon"></i>
<div class="header">
Add Participants
Manage Participants
</div>
<div class="content">
<form class="ui form">
@ -105,15 +124,25 @@ export default {
</div>
</div>
</div>
<div class="field">
<label>Action</label>
<select v-model="action" class="ui dropdown" aria-label="Action">
<option value="add">Add to group</option>
<option value="remove">Remove from group</option>
<option value="promote">Promote to admin</option>
<option value="demote">Demote from admin</option>
</select>
</div>
</form>
</div>
<div class="actions">
<div class="ui approve positive right labeled icon button" :class="{'loading': this.loading}"
@click="handleSubmit" type="button">
Create
Submit
<i class="send icon"></i>
</div>
</div>
</div>
`
}
`,
};

8
src/views/index.html

@ -41,9 +41,10 @@
</div>
<div class="ui three column stackable grid cards">
<app-login></app-login>
<app-login :connected="connected_devices"></app-login>
<app-logout @reload-devices="handleReloadDevice"></app-logout>
<app-reconnect @reload-devices="handleReloadDevice"></app-reconnect>
<app-login-with-code :connected="connected_devices"></app-login-with-code>
</div>
<div class="ui horizontal divider">
@ -127,6 +128,7 @@
</script>
<script type="module">
import AppLogin from "./components/AppLogin.js";
import AppLoginWithCode from "./components/AppLoginWithCode.js";
import AppLogout from "./components/AppLogout.js";
import AppReconnect from "./components/AppReconnect.js";
import SendMessage from "./components/SendMessage.js";
@ -144,7 +146,7 @@
import GroupList from "./components/GroupList.js";
import GroupCreate from "./components/GroupCreate.js";
import GroupJoinWithLink from "./components/GroupJoinWithLink.js";
import GroupAddParticipants from "./components/GroupAddParticipants.js";
import GroupAddParticipants from "./components/GroupManageParticipants.js";
import AccountAvatar from "./components/AccountAvatar.js";
import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js";
@ -170,7 +172,7 @@
Vue.createApp({
components: {
AppLogin, AppLogout, AppReconnect,
AppLogin, AppLoginWithCode, AppLogout, AppReconnect,
SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll,
MessageDelete, MessageUpdate, MessageReact, MessageRevoke,
GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants,

Loading…
Cancel
Save