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 bef27e4..27ce576 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 4.1.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 @@ -1241,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/readme.md b/readme.md index 19d6a03..e336fbc 100644 --- a/readme.md +++ b/readme.md @@ -100,8 +100,8 @@ You can fork or edit this source code ! | Feature | Menu | Method | URL | |---------|------------------------------|--------|-------------------------------| -| ✅ | Login | GET | /app/login | -| ❌ | Login With Pair Code | GET | /app/login-with-code | +| ✅ | 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 | @@ -136,28 +136,32 @@ You can fork or edit this source code ! ❌ = Not Available Yet ``` -### App User Interface - -1. Homepage ![Homepage](https://i.ibb.co.com/d05L4VX/homepage.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 diff --git a/src/config/settings.go b/src/config/settings.go index 35d72b7..136cbe4 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v4.15.0" + AppVersion = "v4.16.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" 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/go.mod b/src/go.mod index 888b7b4..f3a103f 100644 --- a/src/go.mod +++ b/src/go.mod @@ -17,8 +17,8 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/valyala/fasthttp v1.55.0 - go.mau.fi/libsignal v0.1.1-0.20240705162345-47e713a595ab - go.mau.fi/whatsmeow v0.0.0-20240710112833-d732338c041f + go.mau.fi/libsignal v0.1.1 + go.mau.fi/whatsmeow v0.0.0-20240726213518-bb5852f056ca google.golang.org/protobuf v1.34.2 ) @@ -37,7 +37,7 @@ require ( 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 @@ -45,7 +45,7 @@ require ( 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.5.0 // indirect + go.mau.fi/util v0.6.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.27.0 // indirect diff --git a/src/go.sum b/src/go.sum index 7f0824c..4d48acc 100644 --- a/src/go.sum +++ b/src/go.sum @@ -71,6 +71,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE 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= @@ -119,10 +121,14 @@ 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/libsignal v0.1.1-0.20240705162345-47e713a595ab h1:/tnRxsaaG/xBGjXTb6tzTr+XY4T5fQlrGHE1p9ir/wM= go.mau.fi/libsignal v0.1.1-0.20240705162345-47e713a595ab/go.mod h1:R8ovrTezxtUNzCQE5PH30StOQWWeBskBsWE55vMfY9I= +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.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30= go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= go.mau.fi/util v0.5.0 h1:8yELAl+1CDRrwGe9NUmREgVclSs26Z68pTWePHVxuDo= go.mau.fi/util v0.5.0/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4= +go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg= +go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U= 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= @@ -131,6 +137,8 @@ go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1 h1:gpFEqwk7WtbF/8HaOMASKE go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM= go.mau.fi/whatsmeow v0.0.0-20240710112833-d732338c041f h1:ni8K5zVngwOWrrZ1HzwEmvAovj0+p0cq464l9g0/dt0= go.mau.fi/whatsmeow v0.0.0-20240710112833-d732338c041f/go.mod h1:lMW+LxRTakgyNasZwYNB+2uqjKox75GcEfeUXSJhe8I= +go.mau.fi/whatsmeow v0.0.0-20240726213518-bb5852f056ca h1:L0Pc6fi5RevuEASIP6Nd65/HZwCK8wTwm62FEly6UeY= +go.mau.fi/whatsmeow v0.0.0-20240726213518-bb5852f056ca/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= 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/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/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/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/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/index.html b/src/views/index.html index cc01cc4..8f3c029 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -41,9 +41,10 @@

Whatsapp API Multi Device ({{ .AppVersion }
- + +
@@ -127,6 +128,7 @@

Whatsapp API Multi Device ({{ .AppVersion }