Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: login with pair code #171

Merged
merged 11 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
},
})
}
aldinokemal marked this conversation as resolved.
Show resolved Hide resolved

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