From 160003be0dc21f21510184063af33624c7f7c88a Mon Sep 17 00:00:00 2001 From: Keyur Shah Date: Sun, 22 Feb 2026 01:46:55 +0530 Subject: [PATCH 01/26] feat: Initalised The Codebase --- .vscode/launch.json | 8 + servers/soham/whatsapp-client/.gitignore | 2 + .../whatsapp-client/core/qr-browser.api.go | 65 ++++ servers/soham/whatsapp-client/main.go | 83 +++++ servers/whatsapp-server/main.go | 2 +- .../whatsapp-server/src/apis/append.token.go | 2 +- .../whatsapp-server/src/apis/start.number.go | 2 +- .../src/functions/common-req.functions.go | 2 +- .../src/middleware/token-validator.go | 2 +- servers/whatsapp-server/src/whatsapp/index.go | 25 +- .../src/whatsapp/interfaces.go | 4 +- .../whatsapp}/config/config-key.go | 0 .../src => utility/whatsapp}/config/index.go | 0 utility/whatsapp/index.go | 67 ++++ utility/whatsapp/interfaces.go | 341 ++++++++++++++++++ .../whatsapp}/utility/pdf-to-thumb.go | 0 .../whatsapp}/utility/pdf-to-thumb.win.go | 0 17 files changed, 584 insertions(+), 21 deletions(-) create mode 100644 servers/soham/whatsapp-client/.gitignore create mode 100644 servers/soham/whatsapp-client/core/qr-browser.api.go create mode 100644 servers/soham/whatsapp-client/main.go rename {servers/whatsapp-server/src => utility/whatsapp}/config/config-key.go (100%) rename {servers/whatsapp-server/src => utility/whatsapp}/config/index.go (100%) create mode 100644 utility/whatsapp/index.go create mode 100644 utility/whatsapp/interfaces.go rename {servers/whatsapp-server/src => utility/whatsapp}/utility/pdf-to-thumb.go (100%) rename {servers/whatsapp-server/src => utility/whatsapp}/utility/pdf-to-thumb.win.go (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7e2b0f7..3bec0ec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -36,6 +36,14 @@ "args": ["--dev"], "program": "${workspaceFolder}/servers/http-dump/message-dump/main.go" }, + { + "name": "Run Soham Whatsapp Client", + "type": "go", + "request": "launch", + "mode": "auto", + "args": ["--dev"], + "program": "${workspaceFolder}/servers/soham/whatsapp-client/main.go" + }, { "name": "Run Whatsapp Server", "type": "go", diff --git a/servers/soham/whatsapp-client/.gitignore b/servers/soham/whatsapp-client/.gitignore new file mode 100644 index 0000000..710ff81 --- /dev/null +++ b/servers/soham/whatsapp-client/.gitignore @@ -0,0 +1,2 @@ +*.db +server.config.json \ No newline at end of file diff --git a/servers/soham/whatsapp-client/core/qr-browser.api.go b/servers/soham/whatsapp-client/core/qr-browser.api.go new file mode 100644 index 0000000..8867cd6 --- /dev/null +++ b/servers/soham/whatsapp-client/core/qr-browser.api.go @@ -0,0 +1,65 @@ +package whatsapp_client_core + +import ( + "fmt" + + "github.com/gofiber/fiber/v2" +) + +const index = ` + + + + + QR Code Scanner For + + +

Scan The QR Code to Log into whatsapp web account

+ + + +` + +func OpenBrowserWithQr(c *fiber.Ctx) error { + id := c.Params("id") + // println(c.Hostname()) + c.Set("Content-Type", "text/html; charset=utf-8") + return c.Send([]byte(fmt.Sprintf(index, id))) +} diff --git a/servers/soham/whatsapp-client/main.go b/servers/soham/whatsapp-client/main.go new file mode 100644 index 0000000..fdfc49e --- /dev/null +++ b/servers/soham/whatsapp-client/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/rpsoftech/golang-servers/env" + "github.com/rpsoftech/golang-servers/interfaces" + whatsapp_client_core "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-client/core" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/utility/whatsapp" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" +) + +var version string + +// var app *fiber.App + +func main() { + env.LoadEnv("whatsapp-client.env") + println(version) + go func() { + os.RemoveAll("./tmp") + os.Mkdir("./tmp", 0777) + }() + outputLogFolderDir := filepath.Join(env.FindAndReturnCurrentDir(), "whatsapp_server_logs") + + if _, err := utility_functions.Exist(outputLogFolderDir); errors.Is(err, os.ErrNotExist) { + os.MkdirAll(outputLogFolderDir, 0777) + } + whatsapp_interfaces.OutPutFilePath = ReturnOutPutFilePath(env.FindAndReturnCurrentDir()) + container := whatsapp_interfaces.InitSqlContainer() + if whatsapp_config.Env.AUTO_CONNECT_TO_WHATSAPP { + go func() { + for k := range whatsapp_config.WhatsappNumberConfigMap.Tokens { + jidString := whatsapp_config.WhatsappNumberConfigMap.JID[k] + whatsapp_interfaces.ConnectToNumber(jidString, k, container) + } + }() + } + InitFiberServer() + +} + +func InitFiberServer() { + app := fiber.New(fiber.Config{ + BodyLimit: 200 * 1024 * 1024, + ErrorHandler: func(c *fiber.Ctx, err error) error { + mappedError, ok := err.(*interfaces.RequestError) + if !ok { + println(err.Error()) + return c.Status(500).JSON(interfaces.RequestError{ + Code: interfaces.ERROR_INTERNAL_SERVER, + Message: "Some Internal Error", + Name: "Global Error Handler Function", + }) + } + return c.Status(mappedError.StatusCode).JSON(mappedError) + }, + }) + app.Use(logger.New()) + app.Get("/scan/:id", whatsapp_client_core.OpenBrowserWithQr) + app.Use(func(c *fiber.Ctx) error { + return c.Status(fiber.StatusNotFound).SendString("Sorry can't find that!") + }) + hostAndPort := "" + if env.Env.APP_ENV == env.APP_ENV_LOCAL || env.Env.APP_ENV == env.APP_ENV_DEVELOPE { + hostAndPort = "127.0.0.1" + } + hostAndPort = hostAndPort + ":" + env.GetServerPort(env.PORT_KEY) + app.Listen(hostAndPort) +} + +func ReturnOutPutFilePath(currentDir string) string { + t := time.Now() + today := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, t.Nanosecond(), t.Location()).Unix() + return filepath.Join(currentDir, fmt.Sprintf("%d.log.csv", today)) +} diff --git a/servers/whatsapp-server/main.go b/servers/whatsapp-server/main.go index 98a4582..8646495 100644 --- a/servers/whatsapp-server/main.go +++ b/servers/whatsapp-server/main.go @@ -12,10 +12,10 @@ import ( "github.com/rpsoftech/golang-servers/env" "github.com/rpsoftech/golang-servers/interfaces" whatsapp_server_apis "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/apis" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" whatsapp_server_middleware "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/middleware" "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/whatsapp" utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" ) var version string diff --git a/servers/whatsapp-server/src/apis/append.token.go b/servers/whatsapp-server/src/apis/append.token.go index f3bb93f..d3c0050 100644 --- a/servers/whatsapp-server/src/apis/append.token.go +++ b/servers/whatsapp-server/src/apis/append.token.go @@ -2,7 +2,7 @@ package whatsapp_server_apis import ( "github.com/gofiber/fiber/v2" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" ) func AppendTokenInConfigJSON(c *fiber.Ctx) error { diff --git a/servers/whatsapp-server/src/apis/start.number.go b/servers/whatsapp-server/src/apis/start.number.go index 3c0c178..062c5ee 100644 --- a/servers/whatsapp-server/src/apis/start.number.go +++ b/servers/whatsapp-server/src/apis/start.number.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/gofiber/fiber/v2" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" whatsapp_functions "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/functions" "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/whatsapp" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" ) func StartNumber(c *fiber.Ctx) error { diff --git a/servers/whatsapp-server/src/functions/common-req.functions.go b/servers/whatsapp-server/src/functions/common-req.functions.go index 9b571fd..ec92f39 100644 --- a/servers/whatsapp-server/src/functions/common-req.functions.go +++ b/servers/whatsapp-server/src/functions/common-req.functions.go @@ -5,7 +5,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/rpsoftech/golang-servers/interfaces" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" ) func ExtractKeyFromHeader(c *fiber.Ctx, key string) string { diff --git a/servers/whatsapp-server/src/middleware/token-validator.go b/servers/whatsapp-server/src/middleware/token-validator.go index 11977b8..7f3e68d 100644 --- a/servers/whatsapp-server/src/middleware/token-validator.go +++ b/servers/whatsapp-server/src/middleware/token-validator.go @@ -4,7 +4,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/rpsoftech/golang-servers/env" "github.com/rpsoftech/golang-servers/interfaces" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" ) // fiber middleware for jwt diff --git a/servers/whatsapp-server/src/whatsapp/index.go b/servers/whatsapp-server/src/whatsapp/index.go index 993cd07..88f0d81 100644 --- a/servers/whatsapp-server/src/whatsapp/index.go +++ b/servers/whatsapp-server/src/whatsapp/index.go @@ -18,14 +18,18 @@ import ( var SqlContainer *sqlstore.Container var ctx = context.Background() -func InitSqlContainer() { - dbLog := waLog.Stdout("Database", "WARN", true) - // Make sure you add appropriate DB connector imports, e.g. github.com/mattn/go-sqlite3 for SQLite - var err error - SqlContainer, err = sqlstore.New(ctx, "sqlite3", fmt.Sprintf("file:%s?_foreign_keys=on", filepath.Join(env.FindAndReturnCurrentDir(), "WhatsappSuperSecrete.db")), dbLog) - if err != nil { - panic(err) +func InitSqlContainer() *sqlstore.Container { + if SqlContainer == nil { + + dbLog := waLog.Stdout("Database", "WARN", true) + // Make sure you add appropriate DB connector imports, e.g. github.com/mattn/go-sqlite3 for SQLite + var err error + SqlContainer, err = sqlstore.New(ctx, "sqlite3", fmt.Sprintf("file:%s?_foreign_keys=on", filepath.Join(env.FindAndReturnCurrentDir(), "WhatsappSuperSecrete.db")), dbLog) + if err != nil { + panic(err) + } } + return SqlContainer } func ConnectToNumber(jidString string, token string) { @@ -54,14 +58,7 @@ func ConnectToNumber(jidString string, token string) { clientLog := waLog.Stdout("Client", "ERROR", true) client := whatsmeow.NewClient(deviceStore, clientLog) client.EnableAutoReconnect = true - // client. println(client.LastSuccessfulConnect.String()) - - // client.MessengerConfig = &whatsmeow.MessengerConfig{ - // UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", - // BaseURL: "https://web.whatsapp.com", - // } - // client.PairPhone() connection := &WhatsappConnection{Client: client, ConnectionStatus: 0, SyncFinished: false, Token: token} ConnectionMap[token] = connection client.AddEventHandler(connection.eventHandler) diff --git a/servers/whatsapp-server/src/whatsapp/interfaces.go b/servers/whatsapp-server/src/whatsapp/interfaces.go index 4d3c9f7..fe87535 100644 --- a/servers/whatsapp-server/src/whatsapp/interfaces.go +++ b/servers/whatsapp-server/src/whatsapp/interfaces.go @@ -12,9 +12,9 @@ import ( "github.com/mdp/qrterminal/v3" "github.com/rpsoftech/golang-servers/env" "github.com/rpsoftech/golang-servers/interfaces" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" - whatsapp_utility "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/utility" utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" + whatsapp_utility "github.com/rpsoftech/golang-servers/utility/whatsapp/utility" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types/events" diff --git a/servers/whatsapp-server/src/config/config-key.go b/utility/whatsapp/config/config-key.go similarity index 100% rename from servers/whatsapp-server/src/config/config-key.go rename to utility/whatsapp/config/config-key.go diff --git a/servers/whatsapp-server/src/config/index.go b/utility/whatsapp/config/index.go similarity index 100% rename from servers/whatsapp-server/src/config/index.go rename to utility/whatsapp/config/index.go diff --git a/utility/whatsapp/index.go b/utility/whatsapp/index.go new file mode 100644 index 0000000..37c865c --- /dev/null +++ b/utility/whatsapp/index.go @@ -0,0 +1,67 @@ +package whatsapp_interfaces + +import ( + "context" + "fmt" + "path/filepath" + + _ "github.com/mattn/go-sqlite3" + "github.com/rpsoftech/golang-servers/env" + + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/store" + "go.mau.fi/whatsmeow/store/sqlstore" + "go.mau.fi/whatsmeow/types" + waLog "go.mau.fi/whatsmeow/util/log" +) + +var sqlContainer *sqlstore.Container +var ctx = context.Background() + +func InitSqlContainer() *sqlstore.Container { + if sqlContainer == nil { + + dbLog := waLog.Stdout("Database", "WARN", true) + // Make sure you add appropriate DB connector imports, e.g. github.com/mattn/go-sqlite3 for SQLite + var err error + sqlContainer, err = sqlstore.New(ctx, "sqlite3", fmt.Sprintf("file:%s?_foreign_keys=on", filepath.Join(env.FindAndReturnCurrentDir(), "WhatsappSuperSecrete.db")), dbLog) + if err != nil { + panic(err) + } + } + return sqlContainer +} + +func ConnectToNumber(jidString string, token string, sqlContainer *sqlstore.Container) { + // SqlContainer.PutDevice() + if deviceStores, _ := sqlContainer.GetAllDevices(ctx); true { + for _, deviceStore := range deviceStores { + println(deviceStore.ID.User) + } + } + var JID types.JID + if jidString != "" { + JID, _ = types.ParseJID(jidString) + } + var deviceStore *store.Device + if !JID.IsEmpty() { + var err error + deviceStore, err = sqlContainer.GetDevice(ctx, JID) + if err != nil { + println(err.Error()) + } + } + if deviceStore == nil { + deviceStore = sqlContainer.NewDevice() + // deviceStore = types.DEv(number, types.DefaultUserServer) + } + clientLog := waLog.Stdout("Client", "ERROR", true) + client := whatsmeow.NewClient(deviceStore, clientLog) + client.EnableAutoReconnect = true + println(client.LastSuccessfulConnect.String()) + connection := &WhatsappConnection{Client: client, ConnectionStatus: 0, SyncFinished: false, Token: token} + ConnectionMap[token] = connection + client.AddEventHandler(connection.eventHandler) + + connection.ConnectAndGetQRCode() +} diff --git a/utility/whatsapp/interfaces.go b/utility/whatsapp/interfaces.go new file mode 100644 index 0000000..da828bf --- /dev/null +++ b/utility/whatsapp/interfaces.go @@ -0,0 +1,341 @@ +package whatsapp_interfaces + +import ( + "context" + "encoding/base64" + "fmt" + "net/http" + "os" + "reflect" + "strings" + + "github.com/mdp/qrterminal/v3" + "github.com/rpsoftech/golang-servers/env" + "github.com/rpsoftech/golang-servers/interfaces" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" + whatsapp_utility "github.com/rpsoftech/golang-servers/utility/whatsapp/utility" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/types/events" + "google.golang.org/protobuf/proto" +) + +type ( + WhatsappConnection struct { + Client *whatsmeow.Client + Number string + Token string + ConnectionStatus int + QrCodeString string + SyncFinished bool + } + + IWhatsappConnectionMap map[string]*WhatsappConnection +) + +var ( + OutPutFilePath = "" + ConnectionMap = make(IWhatsappConnectionMap) +) + +func (connection *WhatsappConnection) ReturnStatusError() error { + switch connection.ConnectionStatus { + case 0: + return &interfaces.RequestError{ + StatusCode: http.StatusNotFound, + Code: interfaces.ERROR_CONNECTION_NOT_INITIALIZED, + Message: "Connection Not Initialized QR SCANNED", + Name: "ERROR_CONNECTION_NOT_INITIALIZED", + Extra: []string{connection.QrCodeString}, + } + case -1: + return &interfaces.RequestError{ + StatusCode: http.StatusNotFound, + Code: interfaces.ERROR_CONNECTION_LOGGED_OUT, + Message: "Connection Logged Out", + Name: "ERROR_CONNECTION_LOGGED_OUT", + } + } + return nil +} + +func (connection *WhatsappConnection) ConnectAndGetQRCode() { + if connection.Client.Store.ID == nil { + // No ID stored, new login + if whatsapp_config.Env.OPEN_BROWSER_FOR_SCAN { + go func(token string) { + utility_functions.OpenBrowser(fmt.Sprintf("http://127.0.0.1:%s/scan/%s", env.GetServerPort(env.PORT_KEY), token)) + }(connection.Token) + } + qrChan, _ := connection.Client.GetQRChannel(context.Background()) + err := connection.Client.Connect() + if err != nil { + println(err.Error()) + } + for evt := range qrChan { + if evt.Event == "code" { + fmt.Printf("QR code for %s\n", connection.Token) + connection.QrCodeString = evt.Code + // whatsapp_config.WhatsappNumberConfigMap.Tokens[connection.Token] = "Something" + if !whatsapp_config.Env.OPEN_BROWSER_FOR_SCAN { + qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) + } + } else { + fmt.Println("Login event:", evt.Event) + } + } + } else { + // Already logged in, just connect + println("Connected") + err := connection.Client.Connect() + if err != nil { + println(err.Error()) + } + } +} +func (connection *WhatsappConnection) eventHandler(evt interface{}) { + switch v := evt.(type) { + case *events.LoggedOut: + // Send Status + // connection.ConnectionStatus = -1 + connection.Client.Logout(ctx) + connection.Client.Store.Delete(ctx) + println(connection.Number, " Logged Out") + connection.Client.Disconnect() + delete(ConnectionMap, connection.Token) + delete(whatsapp_config.WhatsappNumberConfigMap.Tokens, connection.Token) + delete(whatsapp_config.WhatsappNumberConfigMap.JID, connection.Token) + // whatsapp_config.WhatsappNumberConfigMap.Tokens[connection.Token] = "" + whatsapp_config.WhatsappNumberConfigMap.Save() + go connection.ConnectAndGetQRCode() + case *events.Connected: + // Send Status + connection.Client.Store.Save(ctx) + connection.Number = connection.Client.Store.ID.User + go func() { + whatsapp_config.WhatsappNumberConfigMap.Tokens[connection.Token] = connection.Number + whatsapp_config.WhatsappNumberConfigMap.JID[connection.Token] = connection.Client.Store.ID.String() + whatsapp_config.WhatsappNumberConfigMap.Save() + }() + connection.ConnectionStatus = 1 + println(connection.Number, " Logged In") + case *events.OfflineSyncPreview: + connection.SyncFinished = false + case *events.OfflineSyncCompleted: + connection.SyncFinished = true + default: + fmt.Printf("Event Occurred%s\n", reflect.TypeOf(v)) + } +} +func (connection *WhatsappConnection) SendTextMessage(to []string, msg string) *map[string]interface{} { + response := make(map[string]interface{}) + for _, number := range to { + IsOnWhatsappCheck, err := connection.Client.IsOnWhatsApp(ctx, []string{"+" + number}) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Something Went Wrong %#v\n", number, err)) + // return + response[number] = false + continue + } + if len(IsOnWhatsappCheck) == 0 { + AppendToOutPutFile(fmt.Sprintf("%s,false,Number %s Not On Whatsapp\n", number, number)) + response[number] = false + continue + } + NumberOnWhatsapp := IsOnWhatsappCheck[0] + if !NumberOnWhatsapp.IsIn { + AppendToOutPutFile(fmt.Sprintf("%s,false,Number %s Not On Whatsapp\n", number, number)) + response[number] = false + continue + // return + } + targetJID := NumberOnWhatsapp.JID + fmt.Printf("sending Text To %s\n", number) + response[number] = false + if len(msg) > 0 { + resp, err := connection.Client.SendMessage(context.Background(), targetJID, &waE2E.Message{ + Conversation: proto.String(msg), + }) + if err == nil { + response[number] = resp + } + } + } + return &response +} +func (connection *WhatsappConnection) SendMediaFileBase64(to []string, base64Data string, fileName string, msg string) *map[string]bool { + // pdfBytes, err := os.ReadFile(filePath) + bytesData, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("false,Error While Reading File %#v\n", err)) + return nil + } + return connection.sendMediaFile(to, bytesData, fileName, msg) +} +func (connection *WhatsappConnection) SendMediaFileWithPath(to []string, filePath string, fileName string, msg string) *map[string]bool { + pdfBytes, err := os.ReadFile(filePath) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("false,Error While Reading File %#v\n", err)) + return nil + } + return connection.sendMediaFile(to, pdfBytes, fileName, msg) +} +func (connection *WhatsappConnection) sendMediaFile(to []string, fileByte []byte, fileName string, msg string) *map[string]bool { + response := make(map[string]bool) + var docProto *waE2E.Message + for _, number := range to { + IsOnWhatsappCheck, err := connection.Client.IsOnWhatsApp(ctx, []string{"+" + number}) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Something Went Wrong %#v\n", number, err)) + // return + response[number] = false + continue + } + NumberOnWhatsapp := IsOnWhatsappCheck[0] + if !NumberOnWhatsapp.IsIn { + AppendToOutPutFile(fmt.Sprintf("%s,false,Number %s Not On Whatsapp\n", number, number)) + response[number] = false + continue + // return + } + targetJID := NumberOnWhatsapp.JID + fmt.Printf("sending File To %s\n", number) + if docProto == nil { + extensionName := utility_functions.GetMime(fileName) + if strings.Contains(extensionName, "image") { + resp, err := connection.Client.Upload(context.Background(), fileByte, whatsmeow.MediaImage) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Error While Uploading %#v\n", number, err)) + continue + } + jpegThumbnail, err := utility_functions.ImageThumbnail(fileByte) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Error While Generating Thumbnail %#v\n", number, err)) + } + docProto = &waE2E.Message{ + ImageMessage: &waE2E.ImageMessage{ + Caption: proto.String(msg), + URL: &resp.URL, + Mimetype: proto.String(extensionName), + // FileName: &fileName, + DirectPath: &resp.DirectPath, + JPEGThumbnail: jpegThumbnail, + MediaKey: resp.MediaKey, + FileEncSHA256: resp.FileEncSHA256, + FileSHA256: resp.FileSHA256, + FileLength: &resp.FileLength, + }, + } + } else if strings.Contains(extensionName, "audio") { + resp, err := connection.Client.Upload(context.Background(), fileByte, whatsmeow.MediaAudio) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Error While Uploading %#v\n", number, err)) + continue + } + docProto = &waE2E.Message{ + AudioMessage: &waE2E.AudioMessage{ + // Caption: proto.String(msg), + URL: &resp.URL, + Mimetype: proto.String(extensionName), + // FileName: &fileName, + DirectPath: &resp.DirectPath, + MediaKey: resp.MediaKey, + FileEncSHA256: resp.FileEncSHA256, + FileSHA256: resp.FileSHA256, + FileLength: &resp.FileLength, + }, + } + } else if strings.Contains(extensionName, "video") { + // var thumbResp *whatsmeow.UploadResponse + + thumbBytes, _ := utility_functions.GenerateVideoThumbnail(fileByte, fileName) + // thumbResp, _ := connection.Client.Upload(context.Background(), thumbBytes, whatsmeow.MediaImage) + // } + resp, err := connection.Client.Upload(context.Background(), fileByte, whatsmeow.MediaVideo) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Error While Uploading %#v\n", number, err)) + continue + } + if len(thumbBytes) > 0 { + docProto = &waE2E.Message{ + VideoMessage: &waE2E.VideoMessage{ + Caption: proto.String(msg), + URL: &resp.URL, + Mimetype: proto.String(extensionName), + JPEGThumbnail: thumbBytes, + DirectPath: &resp.DirectPath, + MediaKey: resp.MediaKey, + FileEncSHA256: resp.FileEncSHA256, + FileSHA256: resp.FileSHA256, + FileLength: &resp.FileLength, + }, + } + } else { + docProto = &waE2E.Message{ + VideoMessage: &waE2E.VideoMessage{ + Caption: proto.String(msg), + URL: &resp.URL, + Mimetype: proto.String(extensionName), + DirectPath: &resp.DirectPath, + MediaKey: resp.MediaKey, + FileEncSHA256: resp.FileEncSHA256, + FileSHA256: resp.FileSHA256, + FileLength: &resp.FileLength, + }, + } + } + } else { + resp, err := connection.Client.Upload(context.Background(), fileByte, whatsmeow.MediaDocument) + if err != nil { + AppendToOutPutFile(fmt.Sprintf("%s,false,Error While Uploading %#v\n", number, err)) + continue + } + docProto = &waE2E.Message{ + DocumentMessage: &waE2E.DocumentMessage{ + Caption: proto.String(msg), + URL: &resp.URL, + Mimetype: proto.String(extensionName), + FileName: &fileName, + DirectPath: &resp.DirectPath, + MediaKey: resp.MediaKey, + FileEncSHA256: resp.FileEncSHA256, + FileSHA256: resp.FileSHA256, + FileLength: &resp.FileLength, + }, + } + println("finished uploading") + if strings.Contains(extensionName, "pdf") { + println("PDF to thumb") + thumb, err := whatsapp_utility.ExtractFirstPage(fileByte) + if err == nil && len(thumb) > 0 { + docProto.DocumentMessage.JPEGThumbnail = thumb + } else { + println(err.Error()) + } + } + } + } + response[number] = false + if docProto != nil { + _, err := connection.Client.SendMessage(context.Background(), targetJID, docProto) + if err == nil { + response[number] = true + } + } + } + return &response +} + +func AppendToOutPutFile(text string) { + f, err := os.OpenFile(OutPutFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + panic(err) + } + + defer f.Close() + + if _, err = f.WriteString(text); err != nil { + panic(err) + } +} diff --git a/servers/whatsapp-server/src/utility/pdf-to-thumb.go b/utility/whatsapp/utility/pdf-to-thumb.go similarity index 100% rename from servers/whatsapp-server/src/utility/pdf-to-thumb.go rename to utility/whatsapp/utility/pdf-to-thumb.go diff --git a/servers/whatsapp-server/src/utility/pdf-to-thumb.win.go b/utility/whatsapp/utility/pdf-to-thumb.win.go similarity index 100% rename from servers/whatsapp-server/src/utility/pdf-to-thumb.win.go rename to utility/whatsapp/utility/pdf-to-thumb.win.go From da1a183bc85f7c4c9dc86a7d10d4637ff52a6f62 Mon Sep 17 00:00:00 2001 From: Keyur Shah Date: Sun, 22 Feb 2026 19:28:46 +0530 Subject: [PATCH 02/26] Whatsapp is Workding For Soham Now For the Websocket Server --- functions/whatsapp/common-req.functions.go | 30 ++++++ functions/whatsapp/config/index.go | 53 ++++++++++ .../whatsapp/core}/index.go | 2 +- .../whatsapp/core}/interfaces.go | 5 +- .../whatsapp/middleware/token-validator.go | 96 ++++++++++++++++++ .../whatsapp/config-key.type.go | 6 +- interfaces/whatsapp/whatsapp-env.type.go | 52 ++++++++++ .../soham/whatsapp-client/apis/get-qr.api.go | 42 ++++++++ servers/soham/whatsapp-client/apis/index.go | 9 ++ .../whatsapp-client/core/qr-browser.api.go | 9 +- servers/soham/whatsapp-client/main.go | 18 ++-- servers/whatsapp-server/main.go | 3 +- .../whatsapp-server/src/apis/append.token.go | 2 +- .../whatsapp-server/src/apis/start.number.go | 2 +- .../src/functions/common-req.functions.go | 4 +- .../src/middleware/token-validator.go | 9 +- .../src/whatsapp/interfaces.go | 2 +- utility/whatsapp/config/index.go | 98 ------------------- 18 files changed, 320 insertions(+), 122 deletions(-) create mode 100644 functions/whatsapp/common-req.functions.go create mode 100644 functions/whatsapp/config/index.go rename {utility/whatsapp => functions/whatsapp/core}/index.go (98%) rename {utility/whatsapp => functions/whatsapp/core}/interfaces.go (99%) create mode 100644 functions/whatsapp/middleware/token-validator.go rename utility/whatsapp/config/config-key.go => interfaces/whatsapp/config-key.type.go (63%) create mode 100644 interfaces/whatsapp/whatsapp-env.type.go create mode 100644 servers/soham/whatsapp-client/apis/get-qr.api.go create mode 100644 servers/soham/whatsapp-client/apis/index.go delete mode 100644 utility/whatsapp/config/index.go diff --git a/functions/whatsapp/common-req.functions.go b/functions/whatsapp/common-req.functions.go new file mode 100644 index 0000000..88f0297 --- /dev/null +++ b/functions/whatsapp/common-req.functions.go @@ -0,0 +1,30 @@ +package whatsapp_functions + +import ( + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/rpsoftech/golang-servers/interfaces" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" +) + +func ExtractKeyFromHeader(c *fiber.Ctx, key string) string { + reqHeaders := c.GetReqHeaders() + if tokenString, foundToken := reqHeaders[key]; !foundToken || len(tokenString) != 1 || tokenString[0] == "" { + return "" + } else { + return tokenString[0] + } +} +func ExtractNumberFromCtx(c *fiber.Ctx) (string, error) { + id, ok := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY).(string) + if !ok { + return "", &interfaces.RequestError{ + StatusCode: http.StatusForbidden, + Code: interfaces.INVALID_NUMBER_FROM_TOKEN, + Message: "Invalid Number From Token", + Name: "INVALID_NUMBER_FROM_TOKEN", + } + } + return id, nil +} diff --git a/functions/whatsapp/config/index.go b/functions/whatsapp/config/index.go new file mode 100644 index 0000000..8f674d7 --- /dev/null +++ b/functions/whatsapp/config/index.go @@ -0,0 +1,53 @@ +package whatsapp_config + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/rpsoftech/golang-servers/env" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +var ( + Env *whatsapp_interfaces.EnvInterface + WhatsappNumberConfigMap *whatsapp_interfaces.IServerConfig + WhatsappNumberToIDMap map[string]string + CurrentDirectory string = "" + serverConfigFilePath string = "" +) + +const ServerConfigFileName = "server.config.json" + +func InitaliseWhatsappEnvAndConfig() { + CurrentDirectory = env.FindAndReturnCurrentDir() + serverConfigFilePath = filepath.Join(CurrentDirectory, ServerConfigFileName) + if _, err := utility_functions.Exist(serverConfigFilePath); errors.Is(err, os.ErrNotExist) { + panic(fmt.Errorf("CONFIG_NOT_EXIST_ON_PATH %s", serverConfigFilePath)) + } + WhatsappNumberConfigMap = whatsapp_interfaces.ReadConfigFileAndReturniserverConfig(serverConfigFilePath) + allow_local_no_Auth, err := strconv.ParseBool(os.Getenv(whatsapp_interfaces.Allow_local_no_auth_KEY)) + if err != nil { + log.Fatal(err) + } + auto_connect_to_whatsapp, err := strconv.ParseBool(os.Getenv(whatsapp_interfaces.Auto_Connect_To_Whatsapp_KEY)) + if err != nil { + log.Fatal(err) + } + open_browser_for_scan_KEY, err := strconv.ParseBool(os.Getenv(whatsapp_interfaces.Open_browser_for_scan_KEY)) + if err != nil { + log.Fatal(err) + } + + Env = &whatsapp_interfaces.EnvInterface{ + ALLOW_LOCAL_NO_AUTH: allow_local_no_Auth, + AUTO_CONNECT_TO_WHATSAPP: auto_connect_to_whatsapp, + OPEN_BROWSER_FOR_SCAN: open_browser_for_scan_KEY, + } + WhatsappNumberToIDMap = map[string]string{} + env.ValidateEnv(Env) +} diff --git a/utility/whatsapp/index.go b/functions/whatsapp/core/index.go similarity index 98% rename from utility/whatsapp/index.go rename to functions/whatsapp/core/index.go index 37c865c..e462fc3 100644 --- a/utility/whatsapp/index.go +++ b/functions/whatsapp/core/index.go @@ -1,4 +1,4 @@ -package whatsapp_interfaces +package whatsapp_core import ( "context" diff --git a/utility/whatsapp/interfaces.go b/functions/whatsapp/core/interfaces.go similarity index 99% rename from utility/whatsapp/interfaces.go rename to functions/whatsapp/core/interfaces.go index da828bf..060624a 100644 --- a/utility/whatsapp/interfaces.go +++ b/functions/whatsapp/core/interfaces.go @@ -1,4 +1,4 @@ -package whatsapp_interfaces +package whatsapp_core import ( "context" @@ -11,9 +11,9 @@ import ( "github.com/mdp/qrterminal/v3" "github.com/rpsoftech/golang-servers/env" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" "github.com/rpsoftech/golang-servers/interfaces" utility_functions "github.com/rpsoftech/golang-servers/utility/functions" - whatsapp_config "github.com/rpsoftech/golang-servers/utility/whatsapp/config" whatsapp_utility "github.com/rpsoftech/golang-servers/utility/whatsapp/utility" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" @@ -82,6 +82,7 @@ func (connection *WhatsappConnection) ConnectAndGetQRCode() { qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) } } else { + fmt.Println("Login event:", evt.Event) } } diff --git a/functions/whatsapp/middleware/token-validator.go b/functions/whatsapp/middleware/token-validator.go new file mode 100644 index 0000000..32c5fdc --- /dev/null +++ b/functions/whatsapp/middleware/token-validator.go @@ -0,0 +1,96 @@ +package whatsapp_middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/rpsoftech/golang-servers/env" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" + "github.com/rpsoftech/golang-servers/interfaces" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" +) + +// fiber middleware for jwt +func TokenDecrypter(c *fiber.Ctx) error { + reqHeaders := c.GetReqHeaders() + tokenString, foundToken := reqHeaders[env.RequestTokenHeaderKey] + if !foundToken || len(tokenString) != 1 || tokenString[0] == "" { + if c.IP() == "127.0.0.1" || whatsapp_config.Env.ALLOW_LOCAL_NO_AUTH { + tokenString = []string{"default"} + } else { + + c.Locals(interfaces.REQ_LOCAL_ERROR_KEY, &interfaces.RequestError{ + StatusCode: 403, + Code: interfaces.ERROR_TOKEN_NOT_BEFORE, + Message: "Please Pass Valid Token", + Name: "ERROR_TOKEN_NOT_PASSED", + }) + return c.Next() + } + } + if _, ok := whatsapp_config.WhatsappNumberConfigMap.Tokens[tokenString[0]]; !ok { + c.Locals(interfaces.REQ_LOCAL_ERROR_KEY, &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "Invalid Token", + Name: "ERROR_INVALID_TOKEN", + }) + } else { + c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY, tokenString[0]) + } + return c.Next() +} + +func AllowOnlyValidTokenMiddleWare(c *fiber.Ctx) error { + jwtRawFromLocal := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY) + localError := c.Locals(interfaces.REQ_LOCAL_ERROR_KEY) + + if jwtRawFromLocal == nil { + if localError != nil { + err, ok := localError.(*interfaces.RequestError) + if !ok { + return &interfaces.RequestError{ + StatusCode: 403, + Code: interfaces.ERROR_INTERNAL_SERVER, + Message: "Cannot Cast Error Token", + Name: "NOT_VALID_DECRYPTED_TOKEN", + } + } + return err + } + return &interfaces.RequestError{ + StatusCode: 403, + Code: interfaces.ERROR_TOKEN_NOT_PASSED, + Message: "Invalid Token or token expired", + Name: "ERROR_TOKEN_NOT_PASSED", + } + } + return c.Next() +} + +func AllowOnlyValidLoggedInWhatsapp(c *fiber.Ctx) error { + jwtRawFromLocal := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY) + token, ok := jwtRawFromLocal.(string) + if !ok { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "User Token Not Found", + Name: "ERROR_INVALID_TOKEN", + } + } + if value, ok := whatsapp_config.WhatsappNumberConfigMap.Tokens[token]; !ok { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "Invalid Token", + Name: "ERROR_INVALID_TOKEN", + } + } else if value == "" { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_CONNECTION_LOGGED_OUT, + Message: "Connection Not Connected Please Login to Whatsapp Account", + Name: "ERROR_CONNECTION_LOGGED_OUT", + } + } + return c.Next() +} diff --git a/utility/whatsapp/config/config-key.go b/interfaces/whatsapp/config-key.type.go similarity index 63% rename from utility/whatsapp/config/config-key.go rename to interfaces/whatsapp/config-key.type.go index a582812..14a41cd 100644 --- a/utility/whatsapp/config/config-key.go +++ b/interfaces/whatsapp/config-key.type.go @@ -1,8 +1,8 @@ -package whatsapp_config +package whatsapp_interfaces -const allow_local_no_auth_KEY = "ALLOW_LOCAL_NO_AUTH" +const Allow_local_no_auth_KEY = "ALLOW_LOCAL_NO_AUTH" const Auto_Connect_To_Whatsapp_KEY = "AUTO_CONNECT_TO_WHATSAPP" -const open_browser_for_scan_KEY = "OPEN_BROWSER_FOR_SCAN" +const Open_browser_for_scan_KEY = "OPEN_BROWSER_FOR_SCAN" const ( REQ_LOCAL_KEY_ROLE = "UserRole" diff --git a/interfaces/whatsapp/whatsapp-env.type.go b/interfaces/whatsapp/whatsapp-env.type.go new file mode 100644 index 0000000..3606834 --- /dev/null +++ b/interfaces/whatsapp/whatsapp-env.type.go @@ -0,0 +1,52 @@ +package whatsapp_interfaces + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/rpsoftech/golang-servers/env" + "github.com/rpsoftech/golang-servers/validator" +) + +type ( + EnvInterface struct { + ALLOW_LOCAL_NO_AUTH bool `json:"ALLOW_LOCAL_NO_AUTH" validate:"boolean"` + AUTO_CONNECT_TO_WHATSAPP bool `json:"AUTO_CONNECT_TO_WHATSAPP" validate:"boolean"` + OPEN_BROWSER_FOR_SCAN bool `json:"OPEN_BROWSER_FOR_SCAN" validate:"boolean"` + } + IServerConfig struct { + Tokens map[string]string `json:"tokens" validate:"required"` + JID map[string]string `json:"JID"` + configFilePath string `json:"-"` + } +) + +func ReadConfigFileAndReturniserverConfig(configFilePath string) *IServerConfig { + config := new(IServerConfig) + + dat, err := os.ReadFile(configFilePath) + env.Check(err) + err = json.Unmarshal(dat, config) + // env.Check(err) + if errs := validator.Validator.Validate(config); len(errs) > 0 { + panic(fmt.Errorf("CONFIG_ERROR %#v", errs)) + } + if config.JID == nil { + config.JID = make(map[string]string) + } + config.configFilePath = configFilePath + return config +} + +func (sc *IServerConfig) Save() { + byteJson, err := json.MarshalIndent(sc, "", " ") + if err != nil { + return + } + err = os.WriteFile(sc.configFilePath, byteJson, 0644) + if err != nil { + log.Fatal(err) + } +} diff --git a/servers/soham/whatsapp-client/apis/get-qr.api.go b/servers/soham/whatsapp-client/apis/get-qr.api.go new file mode 100644 index 0000000..b31d9d8 --- /dev/null +++ b/servers/soham/whatsapp-client/apis/get-qr.api.go @@ -0,0 +1,42 @@ +package soham_whatsapp_client_apis + +import ( + "encoding/base64" + "fmt" + "net/http" + + "github.com/gofiber/fiber/v2" + whatsapp_functions "github.com/rpsoftech/golang-servers/functions/whatsapp" + whatsapp_core "github.com/rpsoftech/golang-servers/functions/whatsapp/core" + "github.com/rpsoftech/golang-servers/interfaces" + "github.com/skip2/go-qrcode" +) + +func GetQrCode(c *fiber.Ctx) error { + number, err := whatsapp_functions.ExtractNumberFromCtx(c) + if err != nil { + return err + } + + connection, ok := whatsapp_core.ConnectionMap[number] + if !ok || connection == nil { + return &interfaces.RequestError{ + StatusCode: http.StatusNotFound, + Code: interfaces.ERROR_CONNECTION_NOT_FOUND, + Message: fmt.Sprintf("Number %s Not Found", number), + Name: "ERROR_CONNECTION_NOT_FOUND", + } + } + err = connection.ReturnStatusError() + + if err != nil { + png, _ := qrcode.Encode(connection.QrCodeString, qrcode.High, 512) + return c.JSON(fiber.Map{ + "qrCode": base64.StdEncoding.EncodeToString(png), + "qrCodeData": connection.QrCodeString, + }) + } + return c.JSON(fiber.Map{ + "success": true, + }) +} diff --git a/servers/soham/whatsapp-client/apis/index.go b/servers/soham/whatsapp-client/apis/index.go new file mode 100644 index 0000000..dcd6e5c --- /dev/null +++ b/servers/soham/whatsapp-client/apis/index.go @@ -0,0 +1,9 @@ +package soham_whatsapp_client_apis + +import ( + "github.com/gofiber/fiber/v2" +) + +func AddApis(app fiber.Router) { + app.Get("/qr_code", GetQrCode) +} diff --git a/servers/soham/whatsapp-client/core/qr-browser.api.go b/servers/soham/whatsapp-client/core/qr-browser.api.go index 8867cd6..3be2424 100644 --- a/servers/soham/whatsapp-client/core/qr-browser.api.go +++ b/servers/soham/whatsapp-client/core/qr-browser.api.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gofiber/fiber/v2" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" ) const index = ` @@ -15,6 +16,7 @@ const index = `

Scan The QR Code to Log into whatsapp web account

+

Number: %s