Skip to content

Commit

Permalink
feat: login with pair code (#171)
Browse files Browse the repository at this point in the history
* feat: wip pair code login

feat(app): add LoginWithCode method to IAppService interface for handling phone number login
feat(app): implement LoginWithCode method in App struct to handle phone number login logic
feat(app): implement LoginWithCode method in serviceApp to pair phone number with WhatsApp client

* feat: add api login with code

* feat: add ui getting pair code

* feat: add pair code

* chore: update docs & text

* feat: upgrade version

* chore: update docs

* feat: update image gallery

* fix: show error message

* chore: update repo

* fix: some code improvement
  • Loading branch information
aldinokemal authored Jul 29, 2024
1 parent 8e5b1da commit 7eb1de0
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 38 deletions.
4 changes: 2 additions & 2 deletions docker/golang.Dockerfile
Original file line number Diff line number Diff line change
@@ -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 .
Expand All @@ -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.
Expand Down
43 changes: 42 additions & 1 deletion docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1241,6 +1267,21 @@ components:
device:
type: string
example: '628960561XXX.0:[email protected]'
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:
Expand Down
52 changes: 28 additions & 24 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

var (
AppVersion = "v4.15.0"
AppVersion = "v4.16.0"
AppPort = "3000"
AppDebug = false
AppOs = "AldinoKemal"
Expand Down
1 change: 1 addition & 0 deletions src/domains/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand All @@ -37,15 +37,15 @@ 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
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.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
Expand Down
8 changes: 8 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand Down
15 changes: 15 additions & 0 deletions src/internal/rest/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/error/app_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
23 changes: 23 additions & 0 deletions src/services/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand Down
21 changes: 21 additions & 0 deletions src/validations/app_validation.go
Original file line number Diff line number Diff line change
@@ -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 changes: 61 additions & 0 deletions src/validations/app_validation_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
Loading

0 comments on commit 7eb1de0

Please sign in to comment.