diff --git a/.github/workflows/soham-release-client.yml b/.github/workflows/soham-release-client.yml new file mode 100644 index 0000000..ff5e0d1 --- /dev/null +++ b/.github/workflows/soham-release-client.yml @@ -0,0 +1,48 @@ +name: Soham Release Server + +on: + push: + tags: + - "soham-v*" + +jobs: + + release: + defaults: + run: + shell: bash + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.26" + + - name: Extract version from tag + id: version + shell: bash + run: | + TAG=${GITHUB_REF#refs/tags/soham-v} + echo "VERSION=$TAG" >> $GITHUB_ENV + export CGO_ENABLED=1 + + - name: Debug Runner Info + run: | + echo "OS: $RUNNER_OS" + echo "Ref: $GITHUB_REF" + echo "Workspace: $GITHUB_WORKSPACE" + + - name: Run builder + env: + FILE_SERVER_TOKEN: ${{ secrets.FILE_SERVER_TOKEN }} + KV_TOKEN: ${{ secrets.KEYVALUETOKEN }} + run: | + go run release/soham/whatsapp-client.go ${{ env.VERSION }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2d5a337..25389c3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ main *.exe .DS_St* -dist \ No newline at end of file +dist +SSL \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 7e2b0f7..541218a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,22 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Soham Client Release Server", + "type": "go", + "request": "launch", + "mode": "debug", + "args": ["dev","--dev"], + "program": "${workspaceFolder}/release/soham/whatsapp-client.go" + }, + { + "name": "Run Soham Whatsapp GUI", + "type": "go", + "request": "launch", + "mode": "auto", + "args": ["--dev"], + "program": "${workspaceFolder}/apps/sohan/whatsapp/main.go" + }, { "name": "Run Telegram Server", "type": "go", @@ -36,6 +52,22 @@ "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 Soham Whatsapp Server", + "type": "go", + "request": "launch", + "mode": "auto", + "args": ["--dev"], + "program": "${workspaceFolder}/servers/soham/whatsapp-server/main.go" + }, { "name": "Run Whatsapp Server", "type": "go", diff --git a/apps/sohan/whatsapp/WhatsappSuperSecrete.db b/apps/sohan/whatsapp/WhatsappSuperSecrete.db new file mode 100644 index 0000000..0d90462 Binary files /dev/null and b/apps/sohan/whatsapp/WhatsappSuperSecrete.db differ diff --git a/apps/sohan/whatsapp/auto-download/index.go b/apps/sohan/whatsapp/auto-download/index.go new file mode 100644 index 0000000..30ab6b8 --- /dev/null +++ b/apps/sohan/whatsapp/auto-download/index.go @@ -0,0 +1,251 @@ +package sohan_whatsapp_auto_download + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "runtime" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" + soham_whatsapp_gui_config "github.com/rpsoftech/golang-servers/apps/sohan/whatsapp/config" +) + +const VersionFileName = "client-version.json" + +type VersionInfo struct { + Version int `json:"version"` + URL string `json:"url"` + SHA256 string `json:"sha256"` +} + +type progressReader struct { + reader io.Reader + total int64 + read int64 + progress *widget.ProgressBar +} + +func (p *progressReader) Read(buf []byte) (int, error) { + + n, err := p.reader.Read(buf) + + p.read += int64(n) + + if p.total > 0 { + + value := float64(p.read) / float64(p.total) + + fyne.Do(func() { + p.progress.SetValue(value) + }) + } + + return n, err +} + +var checkAndRunCalled = false + +func sha256File(path string) (string, error) { + + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + hash := sha256.New() + + _, err = io.Copy(hash, file) + if err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// OLD Function +// func downloadFile(url string, path string) error { + +// resp, err := http.Get(url) +// if err != nil { +// return err +// } +// defer resp.Body.Close() + +// out, err := os.Create(path) +// if err != nil { +// return err +// } +// defer out.Close() + +// _, err = io.Copy(out, resp.Body) + +// return err +// } + +func GetVersionEndpoint() string { + + switch fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) { + // soham_go_wbot_darwin_amd64 + case "windows_amd64": + return "https://keyvalue.rpso.in/public/soham_go_wbot_windows_amd64" + + case "darwin_amd64": + return "https://keyvalue.rpso.in/public/soham_go_wbot_darwin_amd64" + case "darwin_arm64": + return "https://keyvalue.rpso.in/public/soham_go_wbot_darwin_arm64" + + default: + return "" + } +} + +func CheckAndDownload(progress *widget.ProgressBar, win fyne.Window) string { + serverBinary := "whatsapp-client.o" + if runtime.GOOS == "windows" { + serverBinary = "whatsapp-client.exe" + } + if checkAndRunCalled { + return serverBinary + } + endpoint := GetVersionEndpoint() + if endpoint == "" { + panic(fmt.Errorf("Endpoint Not Found For %s and %s", runtime.GOOS, runtime.GOARCH)) + } + resp, err := http.Get(endpoint) + if err != nil { + fmt.Println("Error in Downloading client file") + panic(err) + } + defer resp.Body.Close() + var cloud VersionInfo + json.NewDecoder(resp.Body).Decode(&cloud) + + var local VersionInfo + + data, err := os.ReadFile(VersionFileName) + if err == nil { + json.Unmarshal(data, &local) + } + + tmpFile := serverBinary + ".tmp" + + needDownload := false + + if _, err := os.Stat(serverBinary); os.IsNotExist(err) { + needDownload = true + } + + if local.Version != cloud.Version { + needDownload = true + } + + if needDownload { + os.Remove(tmpFile) + fyne.DoAndWait(func() { + win.Show() + }) + err := downloadFileWithProgress(cloud.URL, tmpFile, progress) + if err != nil { + if _, err := os.Stat(serverBinary); os.IsNotExist(err) { + panic(fmt.Errorf("File Downloading Failed")) + // needDownload = true + } + return serverBinary + } + + hash, err := sha256File(tmpFile) + if err != nil { + return serverBinary + } + + if hash != cloud.SHA256 { + os.Remove(tmpFile) + return serverBinary + } + err = replaceBinarySafe(tmpFile, serverBinary) + if err != nil { + log.Println("Binary replace failed:", err) + return serverBinary + } + vdata, _ := json.Marshal(cloud) + os.WriteFile(VersionFileName, vdata, 0644) + } + checkAndRunCalled = true + return serverBinary +} + +func downloadFileWithProgress(url string, filepath string, progress *widget.ProgressBar) error { + client := &http.Client{ + Timeout: 500 * time.Second, + } + resp, err := client.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + total := resp.ContentLength + if total <= 0 { + // unknown size → indeterminate progress + fyne.Do(func() { + progress.SetValue(0) + }) + } + + pr := &progressReader{ + reader: resp.Body, + total: total, + progress: progress, + } + + _, err = io.Copy(out, pr) + return err +} + +func replaceBinarySafe(tmpFile string, serverBinary string) error { + + // stop server first + if soham_whatsapp_gui_config.ServerCmd != nil && soham_whatsapp_gui_config.ServerCmd.Process != nil { + soham_whatsapp_gui_config.ServerCmd.Process.Kill() + time.Sleep(1 * time.Second) + } + + // backup existing binary + backup := serverBinary + ".old" + + os.Remove(backup) + + if _, err := os.Stat(serverBinary); err == nil { + os.Rename(serverBinary, backup) + } + + // move new binary + err := os.Rename(tmpFile, serverBinary) + if err != nil { + return err + } + + // macOS / Linux need execute permission + if runtime.GOOS != "windows" { + os.Chmod(serverBinary, 0755) + } + + // cleanup backup + os.Remove(backup) + + return nil +} diff --git a/apps/sohan/whatsapp/client-version.json b/apps/sohan/whatsapp/client-version.json new file mode 100644 index 0000000..b58e5ef --- /dev/null +++ b/apps/sohan/whatsapp/client-version.json @@ -0,0 +1 @@ +{"version":1,"url":"https://files.rpso.in/static/soham/wbot/darwin_arm64/whatsapp-client.o","sha256":"0d2cd272835b72535a9f4b8e9f6cfb58f59772b74d0e6eb9461b3ec120ad497b"} \ No newline at end of file diff --git a/apps/sohan/whatsapp/config/index.go b/apps/sohan/whatsapp/config/index.go new file mode 100644 index 0000000..a0eab64 --- /dev/null +++ b/apps/sohan/whatsapp/config/index.go @@ -0,0 +1,71 @@ +package soham_whatsapp_gui_config + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/rpsoftech/golang-servers/env" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + "github.com/rpsoftech/golang-servers/validator" +) + +var ServerConfigFilePath = "" +var ServerCmd *exec.Cmd + +func init() { + CurrentDirectory := env.FindAndReturnCurrentDir() + ServerConfigFilePath = filepath.Join(CurrentDirectory, whatsapp_config.ServerConfigFileName) +} + +func ValidateConfig() bool { + if _, err := utility_functions.Exist(ServerConfigFilePath); errors.Is(err, os.ErrNotExist) { + panic(fmt.Errorf("CONFIG_NOT_EXIST_ON_PATH %s", ServerConfigFilePath)) + } + _, err := readConfigFileAndReturniserverConfig(ServerConfigFilePath) + if err != nil { + return false + // panic(err) + } + return true +} + +func readConfigFileAndReturniserverConfig(configFilePath string) (*whatsapp_interfaces.IServerConfig, error) { + config := new(whatsapp_interfaces.IServerConfig) + + dat, err := os.ReadFile(configFilePath) + // env.Check(err) + if err != nil { + return nil, err + } + err = json.Unmarshal(dat, config) + // env.Check(err) + if err != nil { + return nil, 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.SetConfigPath(configFilePath) + return config, nil +} + +func SaveConfig(config *whatsapp_interfaces.IServerConfig) { + byteJson, err := json.MarshalIndent(config, "", " ") + if err != nil { + return + } + err = os.WriteFile(config.GetConfigPath(), byteJson, 0644) + if err != nil { + log.Fatal(err) + } +} diff --git a/apps/sohan/whatsapp/gui.lock b/apps/sohan/whatsapp/gui.lock new file mode 100644 index 0000000..b26915b --- /dev/null +++ b/apps/sohan/whatsapp/gui.lock @@ -0,0 +1 @@ +14605 \ No newline at end of file diff --git a/apps/sohan/whatsapp/icon.png b/apps/sohan/whatsapp/icon.png new file mode 100644 index 0000000..32b264b Binary files /dev/null and b/apps/sohan/whatsapp/icon.png differ diff --git a/apps/sohan/whatsapp/main.go b/apps/sohan/whatsapp/main.go new file mode 100644 index 0000000..bb4dd18 --- /dev/null +++ b/apps/sohan/whatsapp/main.go @@ -0,0 +1,492 @@ +package main + +import ( + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "syscall" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + sohan_whatsapp_auto_download "github.com/rpsoftech/golang-servers/apps/sohan/whatsapp/auto-download" + soham_whatsapp_gui_config "github.com/rpsoftech/golang-servers/apps/sohan/whatsapp/config" + "github.com/rpsoftech/golang-servers/env" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +////////////////////////////////////////////////////// +// GLOBALS +////////////////////////////////////////////////////// + +var mainWindow fyne.Window +var trayStatusItem *fyne.MenuItem +var version string +var configFile = "config.json" +var serverLock = "server.lock" +var guiLock = "gui.lock" + +////////////////////////////////////////////////////// +// CONFIG STRUCT +////////////////////////////////////////////////////// + +type Config struct { + Token string `json:"token"` + Number string `json:"number"` +} + +////////////////////////////////////////////////////// +// UUID VALIDATION +////////////////////////////////////////////////////// + +func validUUID(uuid string) bool { + + reg := regexp.MustCompile( + `^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[1-5][a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$`, + ) + + return reg.MatchString(uuid) +} + +////////////////////////////////////////////////////// +// SINGLE GUI INSTANCE +////////////////////////////////////////////////////// + +func validateGUILOCK() bool { + data, err := os.ReadFile(guiLock) + + if err != nil { + return false + } + + pid, err := strconv.Atoi(string(data)) + + if err != nil { + return false + } + + process, err := os.FindProcess(pid) + + if err != nil { + return false + } + err = process.Signal(syscall.Signal(0)) + return err == nil + +} +func preventMultipleGUI() { + + if _, err := os.Stat(guiLock); err == nil { + if validateGUILOCK() { + os.Exit(0) + } + } + + os.WriteFile(guiLock, []byte(strconv.Itoa(os.Getpid())), 0644) +} + +////////////////////////////////////////////////////// +// SERVER INSTANCE CHECK +////////////////////////////////////////////////////// + +func serverRunning() bool { + + data, err := os.ReadFile(serverLock) + + if err != nil { + return false + } + + pid, err := strconv.Atoi(string(data)) + + if err != nil { + return false + } + + process, err := os.FindProcess(pid) + + if err != nil { + return false + } + + err = process.Signal(syscall.Signal(0)) + + return err == nil +} + +////////////////////////////////////////////////////// +// START SERVER +////////////////////////////////////////////////////// + +func startServer(a fyne.App) { + if serverRunning() { + return + } + progressBar, progressWin := showDownloadProgress(a) + + go func() { + serverBinary := sohan_whatsapp_auto_download.CheckAndDownload(progressBar, progressWin) + // start server + fyne.Do(func() { + progressWin.Close() + }) + for { + updateTrayStatus("🟡 Starting server...") + soham_whatsapp_gui_config.ServerCmd = exec.Command(filepath.Join(env.FindAndReturnCurrentDir(), serverBinary)) + serverCmd := soham_whatsapp_gui_config.ServerCmd + if env.IsDev { + stdout, err := serverCmd.StdoutPipe() + if err != nil { + log.Println("stdout pipe error:", err) + time.Sleep(3 * time.Second) + continue + } + + stderr, err := serverCmd.StderrPipe() + if err != nil { + log.Println("stderr pipe error:", err) + time.Sleep(3 * time.Second) + continue + } + go utility_functions.StreamLogs(stdout, "SERVER") + go utility_functions.StreamLogs(stderr, "SERVER") + } + err := soham_whatsapp_gui_config.ServerCmd.Start() + if err != nil { + updateTrayStatus("🔴 Server failed") + time.Sleep(50 * time.Second) + continue + } + + updateTrayStatus("🟢 Server running") + err = soham_whatsapp_gui_config.ServerCmd.Wait() + updateTrayStatus("🔴 Server stopped") + time.Sleep(3 * time.Second) + + } + }() +} + +////////////////////////////////////////////////////// +//// Stream Logs ///////////////////////////////////// +////////////////////////////////////////////////////// + +////////////////////////////////////////////////////// +// RESTART SERVER +////////////////////////////////////////////////////// + +func restartServer() { + + if soham_whatsapp_gui_config.ServerCmd != nil && soham_whatsapp_gui_config.ServerCmd.Process != nil { + soham_whatsapp_gui_config.ServerCmd.Process.Kill() + } + +} + +////////////////////////////////////////////////////// +// SAVE CONFIG +////////////////////////////////////////////////////// + +func saveConfig(token, number string) { + config := new(whatsapp_interfaces.IServerConfig) + config.Tokens = make(map[string]string) + config.JID = make(map[string]string) + config.Tokens[token] = number + config.SetConfigPath(soham_whatsapp_gui_config.ServerConfigFilePath) + soham_whatsapp_gui_config.SaveConfig(config) +} + +////////////////////////////////////////////////////// +// LOAD CONFIG +////////////////////////////////////////////////////// + +func loadConfig() bool { + + return soham_whatsapp_gui_config.ValidateConfig() +} + +////////////////////////////////////////////////////// +// SUCCESS SCREEN +////////////////////////////////////////////////////// + +func successScreen() fyne.CanvasObject { + + msg := widget.NewLabelWithStyle( + "Configuration Successful", + fyne.TextAlignCenter, + fyne.TextStyle{Bold: true}, + ) + + restartBtn := widget.NewButton("Restart Server", func() { + + msg.SetText("Restarting Server...") + restartServer() + }) + + // resetBtn := widget.NewButton("Reset Config", func() { + + // os.Remove(configFile) + // mainWindow.SetContent(configForm(a)) + // }) + + buttons := container.NewHBox( + layout.NewSpacer(), + restartBtn, + // resetBtn, + layout.NewSpacer(), + ) + + return container.NewVBox( + layout.NewSpacer(), + container.NewHBox(layout.NewSpacer(), msg, layout.NewSpacer()), + layout.NewSpacer(), + buttons, + layout.NewSpacer(), + ) +} + +////////////////////////////////////////////////////// +// CONFIG FORM +////////////////////////////////////////////////////// + +func configForm(a fyne.App) fyne.CanvasObject { + + token := widget.NewEntry() + number := widget.NewEntry() + + token.SetPlaceHolder("UUID Token") + number.SetPlaceHolder("Phone Number") + + status := widget.NewLabel("") + + saveBtn := widget.NewButton("Save Config", func() { + + if !validUUID(token.Text) { + status.SetText("Invalid UUID Token") + return + } + + if number.Text == "" { + status.SetText("Number required") + return + } + + saveConfig(token.Text, number.Text) + + startServer(a) + + mainWindow.SetContent(successScreen()) + + go func() { + time.Sleep(3 * time.Second) + fyne.Do(func() { + mainWindow.Hide() + }) + + }() + }) + + row1 := container.NewGridWithColumns(2, + widget.NewLabel("Token"), + token, + ) + + row2 := container.NewGridWithColumns(2, + widget.NewLabel("Number"), + number, + ) + + // embedded logo + logo := canvas.NewImageFromFile("icon.png") + + logo.FillMode = canvas.ImageFillContain + logo.SetMinSize(fyne.NewSize(120, 80)) + + logoBox := container.NewHBox( + layout.NewSpacer(), + logo, + layout.NewSpacer(), + ) + + return container.NewVBox( + logoBox, + layout.NewSpacer(), + row1, + row2, + layout.NewSpacer(), + container.NewHBox(layout.NewSpacer(), saveBtn, layout.NewSpacer()), + layout.NewSpacer(), + container.NewHBox(layout.NewSpacer(), status, layout.NewSpacer()), + layout.NewSpacer(), + ) +} + +func showDownloadProgress(a fyne.App) (*widget.ProgressBar, fyne.Window) { + + var win fyne.Window + fyne.DoAndWait(func() { + win = a.NewWindow("Downloading Update") + }) + + progress := widget.NewProgressBar() + + progress.SetValue(0) + + fyne.DoAndWait(func() { + // win = a.NewWindow("Downloading Update") + win.SetContent( + container.NewVBox( + widget.NewLabel("Downloading server update..."), + progress, + ), + ) + win.Resize(fyne.NewSize(400, 120)) + win.CenterOnScreen() + }) + + return progress, win +} + +////////////////////////////////////////////////////// +// Update Tray Status +////////////////////////////////////////////////////// + +func updateTrayStatus(status string) { + + if trayStatusItem == nil { + return + } + + fyne.Do(func() { + trayStatusItem.Label = status + }) +} + +// //////////////////////////////////////////////////// +// SET Env For Client +// //////////////////////////////////////////////////// +// APP_ENV="LOCAL" +// PORT="4000" +// SOCKET_URL="ws://localhost:4001/whatsapp-client/ws" +// ALLOW_LOCAL_NO_AUTH=false +// AUTO_CONNECT_TO_WHATSAPP=true +// OPEN_BROWSER_FOR_SCAN=true +func SetEnv() { + if os.Getenv("PORT") == "" { + os.Setenv("PORT", "4000") + } + if os.Getenv("SOCKET_URL") == "" { + os.Setenv("SOCKET_URL", "ws://localhost:4001/whatsapp-client/ws") + } + if os.Getenv("ALLOW_LOCAL_NO_AUTH") == "" { + os.Setenv("ALLOW_LOCAL_NO_AUTH", "false") + } + if os.Getenv("AUTO_CONNECT_TO_WHATSAPP") == "" { + os.Setenv("AUTO_CONNECT_TO_WHATSAPP", "true") + } + if os.Getenv("OPEN_BROWSER_FOR_SCAN") == "" { + os.Setenv("OPEN_BROWSER_FOR_SCAN", "true") + } +} + +////////////////////////////////////////////////////// +// MAIN +////////////////////////////////////////////////////// + +func main() { + if env.IsDev { + version = "dev" + } + log.Printf("Starting WABOT GUI Version %s", version) + if os.Getenv("APP_ENV") == "" { + os.Setenv("APP_ENV", "PRODUCTION") + } + env.LoadEnv(filepath.Join(env.FindAndReturnCurrentDir(), "whatsapp-client.env")) + SetEnv() + log.Printf("ENV Settled") + preventMultipleGUI() + defer os.Remove(guiLock) + a := app.New() + // a.Settings().SetTheme(theme.) + + window := a.NewWindow("RP Softech Config Tool") + window.Resize(fyne.NewSize(520, 380)) + window.CenterOnScreen() + + mainWindow = window + + // embedded icon + iconResource, err := fyne.LoadResourceFromPath("icon.png") + if err == nil { + window.SetIcon(iconResource) + } + // window.SetIcon(icon) + + ////////////////////////////////////////////////// + // SYSTEM TRAY + ////////////////////////////////////////////////// + + if desk, ok := a.(desktop.App); ok { + + trayStatusItem = fyne.NewMenuItem("🟡 Starting...", nil) + trayStatusItem.Disabled = true + + showItem := fyne.NewMenuItem("Open Config", func() { + window.Show() + window.RequestFocus() + }) + restartItem := fyne.NewMenuItem("Restart Server", func() { + restartServer() + }) + quitItem := fyne.NewMenuItem("Quit", func() { + os.Remove(guiLock) + a.Quit() + }) + + menu := fyne.NewMenu("WABOT Utility", + trayStatusItem, + fyne.NewMenuItemSeparator(), + showItem, + restartItem, + fyne.NewMenuItemSeparator(), + quitItem, + ) + + desk.SetSystemTrayMenu(menu) + desk.SetSystemTrayIcon(iconResource) + } + + window.SetCloseIntercept(func() { + window.Hide() + }) + + ////////////////////////////////////////////////// + // START LOGIC + ////////////////////////////////////////////////// + + if loadConfig() { + window.SetContent(successScreen()) + go func() { + time.Sleep(3 * time.Second) + startServer(a) + time.Sleep(3 * time.Second) + fyne.Do(func() { + window.Hide() + }) + }() + } else { + window.SetContent(configForm(a)) + } + window.Show() + + a.Run() +} diff --git a/apps/sohan/whatsapp/server.config.json b/apps/sohan/whatsapp/server.config.json new file mode 100644 index 0000000..8ad3b13 --- /dev/null +++ b/apps/sohan/whatsapp/server.config.json @@ -0,0 +1,6 @@ +{ + "tokens": { + "1a7f33ae-3f54-5d53-b36e-9544017e87dc": "9428393489" + }, + "JID": {} +} \ No newline at end of file diff --git a/apps/sohan/whatsapp/whatsapp-client.o b/apps/sohan/whatsapp/whatsapp-client.o new file mode 100755 index 0000000..df0b985 Binary files /dev/null and b/apps/sohan/whatsapp/whatsapp-client.o differ 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/functions/whatsapp/core/index.go b/functions/whatsapp/core/index.go new file mode 100644 index 0000000..f59aef5 --- /dev/null +++ b/functions/whatsapp/core/index.go @@ -0,0 +1,65 @@ +package whatsapp_core + +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} + client.AddEventHandler(connection.eventHandler) + connection.ConnectAndGetQRCode() +} diff --git a/functions/whatsapp/core/interfaces.go b/functions/whatsapp/core/interfaces.go new file mode 100644 index 0000000..a654881 --- /dev/null +++ b/functions/whatsapp/core/interfaces.go @@ -0,0 +1,344 @@ +package whatsapp_core + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "net/http" + "os" + "reflect" + "strings" + + "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_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() { + ConnectionMap[connection.Token] = connection + if connection.Client.Store.ID == nil { + // No ID stored, new login + if whatsapp_config.Env.OPEN_BROWSER_FOR_SCAN { + go func(token string) { + log.Printf("Opening Browser for Token %s", token) + 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.Disconnect() + if err := connection.Client.Logout(ctx); err != nil { + connection.Client.Disconnect() + connection.Client.Store.Delete(ctx) + } + println(connection.Number, " Logged Out") + delete(ConnectionMap, connection.Token) + delete(whatsapp_config.WhatsappNumberConfigMap.JID, 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.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/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/go.mod b/go.mod index f473d28..36b3dbf 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,19 @@ go 1.25.0 require ( cloud.google.com/go/firestore v1.21.0 firebase.google.com/go/v4 v4.19.0 + fyne.io/fyne/v2 v2.7.3 github.com/fxamacker/cbor/v2 v2.9.0 github.com/gen2brain/go-fitz v1.24.15 github.com/go-faker/faker/v4 v4.7.0 github.com/go-playground/validator/v10 v10.30.1 github.com/go-sql-driver/mysql v1.9.3 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 - github.com/gofiber/fiber/v2 v2.52.11 + github.com/gofiber/contrib/v3/websocket v1.0.0 + github.com/gofiber/fiber/v2 v2.52.12 + github.com/gofiber/fiber/v3 v3.1.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 github.com/mattn/go-sqlite3 v1.14.34 github.com/mdp/qrterminal/v3 v3.2.1 @@ -24,10 +28,11 @@ require ( github.com/rpsoftech/mysqldump v0.0.0-20250611190005-213bcf343081 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/surrealdb/surrealdb.go v1.3.0 - go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 + go.mau.fi/whatsmeow v0.0.0-20260227112304-c9652e4448a2 go.mongodb.org/mongo-driver v1.17.9 golang.org/x/crypto v0.48.0 - google.golang.org/api v0.267.0 + golang.org/x/sys v0.41.0 + google.golang.org/api v0.269.0 google.golang.org/protobuf v1.36.11 ) @@ -42,6 +47,8 @@ require ( cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/storage v1.60.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect + fyne.io/systray v1.12.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect @@ -52,18 +59,33 @@ require ( github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/coder/websocket v1.8.14 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/ebitengine/purego v0.10.0-alpha.5 // indirect + github.com/ebitengine/purego v0.10.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect + github.com/fasthttp/websocket v1.5.12 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fredbi/uri v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fyne-io/gl-js v0.2.0 // indirect + github.com/fyne-io/glfw-js v0.3.0 // indirect + github.com/fyne-io/image v0.1.1 // indirect + github.com/fyne-io/oksvg v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-text/render v0.2.0 // indirect + github.com/go-text/typesetting v0.3.4 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/gofiber/schema v1.7.0 // indirect + github.com/gofiber/utils/v2 v2.0.2 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -71,7 +93,10 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/hack-pad/go-indexeddb v0.3.2 // indirect + github.com/hack-pad/safejs v0.1.1 // indirect + github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect + github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/jupiterrider/ffi v0.6.0 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -79,10 +104,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect github.com/montanaflynn/stats v0.7.1 // indirect - github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect + github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rs/zerolog v1.34.0 // indirect + github.com/rymdport/portal v0.4.2 // indirect + github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/tinylib/msgp v1.6.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect github.com/vektah/gqlparser/v2 v2.5.32 // indirect @@ -91,6 +126,7 @@ require ( github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + github.com/yuin/goldmark v1.7.16 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/util v0.9.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -105,17 +141,17 @@ require ( go.uber.org/atomic v1.11.0 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/image v0.36.0 // indirect - golang.org/x/net v0.50.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect + google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/grpc v1.79.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 375d9b4..f03ba9b 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,12 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= firebase.google.com/go/v4 v4.19.0 h1:f5NMlC2YHFsncz00c2+ecBr+ZYlRMhKIhj1z8Iz0lD8= firebase.google.com/go/v4 v4.19.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs= +fyne.io/fyne/v2 v2.7.3 h1:xBT/iYbdnNHONWO38fZMBrVBiJG8rV/Jypmy4tVfRWE= +fyne.io/fyne/v2 v2.7.3/go.mod h1:gu+dlIcZWSzKZmnrY8Fbnj2Hirabv2ek+AKsfQ2bBlw= +fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM= +fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= @@ -65,8 +71,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= -github.com/ebitengine/purego v0.10.0-alpha.5 h1:IUIZ1pu0wnpxrn7o6utj8AeoZBS2upI11kLcddBF414= -github.com/ebitengine/purego v0.10.0-alpha.5/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= @@ -77,16 +83,36 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE= +github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko= +github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= +github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= +github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= +github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= +github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= +github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8= +github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo= github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo= github.com/go-faker/faker/v4 v4.7.0 h1:VboC02cXHl/NuQh5lM2W8b87yp4iFXIu59x4w0RZi4E= github.com/go-faker/faker/v4 v4.7.0/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -106,9 +132,25 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1 github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU= +github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY= +github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos= +github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.11 h1:5f4yzKLcBcF8ha1GQTWB+mpblWz3Vz6nSAbTL31HkWs= -github.com/gofiber/fiber/v2 v2.52.11/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofiber/contrib/v3/websocket v1.0.0 h1:HZNjtiq1HbfTxMOftrwuHtafmwPV8ia2WU2BX0MX7Gg= +github.com/gofiber/contrib/v3/websocket v1.0.0/go.mod h1:h+1Cdn9CRPaF1XBMbmyuhPJuQPo/IXdMrX8YqmLxIFY= +github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= +github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY= +github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU= +github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg= +github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk= +github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI= +github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -126,6 +168,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -136,14 +180,26 @@ github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= +github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= +github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= +github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jupiterrider/ffi v0.6.0 h1:UX378KcZvH5c8qgLi9KL/bL82SZTHdRspZ+jj7bvBng= github.com/jupiterrider/ffi v0.6.0/go.mod h1:PqZ5Go6X9by8CIXgfprxfMPYmn8oT5m2O7AA56s64bY= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM= @@ -165,9 +221,17 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= -github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ= +github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -178,21 +242,35 @@ github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfS github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rpsoftech/mysqldump v0.0.0-20250611190005-213bcf343081 h1:BhhQEZcCQNHuJ80dzWHzmroYro0rvijuPaIM12atvNg= github.com/rpsoftech/mysqldump v0.0.0-20250611190005-213bcf343081/go.mod h1:r/BoxnMDP7PN0Hbyysah74N/VHbv6KrnIZ6f7fRsljk= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU= +github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 h1:McifyVxygw1d67y6vxUqls2D46J8W9nrki9c8c0eVvE= +github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761/go.mod h1:Vi9gvHvTw4yCUHIznFl5TPULS7aXwgaTByGeBY75Wko= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shamaton/msgpack/v3 v3.1.0 h1:jsk0vEAqVvvS9+fTZ5/EcQ9tz860c9pWxJ4Iwecz8gU= +github.com/shamaton/msgpack/v3 v3.1.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/surrealdb/surrealdb.go v1.3.0 h1:/ccM9zQnx+SXYjQh1eFxcc0UagDd3VDHNUzmbyU/QEc= github.com/surrealdb/surrealdb.go v1.3.0/go.mod h1:ju3vn9OHXde9Ulvc7/fP9I8ylkiapOdBSdrEs2PmTtA= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= @@ -212,14 +290,16 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI= -go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 h1:hsmlwsM+VqfF70cpdZEeIUKer2XWCQmQPK0u0tHy3ZQ= -go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.mau.fi/whatsmeow v0.0.0-20260227112304-c9652e4448a2 h1:tYSfEoDVfPEWWuNgbYzyaX6TmWwlplW6NktbaGsVAb0= +go.mau.fi/whatsmeow v0.0.0-20260227112304-c9652e4448a2/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -244,6 +324,8 @@ go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZY go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= @@ -257,8 +339,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -295,22 +377,26 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= -google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg= +google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= -google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc= -google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI= -google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s= -google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 h1:RxhCsti413yL0IjU9dVvuTbCISo8gs3RW1jPMStck+4= +google.golang.org/genproto v0.0.0-20260226221140-a57be14db171/go.mod h1:uhvzakVEqAuXU3TC2JCsxIRe5f77l+JySE3EqPoMyqM= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= diff --git a/interfaces/ssl.config.type.go b/interfaces/ssl.config.type.go new file mode 100644 index 0000000..62c7afe --- /dev/null +++ b/interfaces/ssl.config.type.go @@ -0,0 +1,7 @@ +package interfaces + +type SSLConfig struct { + KeyFilePath string `json:"key" validate:"required"` + CertFilePath string `json:"cert" validate:"required"` + // CAFilePaths []string `json:"caFilePaths"` +} diff --git a/servers/whatsapp-server/src/config/config-key.go b/interfaces/whatsapp/config-key.type.go similarity index 63% rename from servers/whatsapp-server/src/config/config-key.go rename to interfaces/whatsapp/config-key.type.go index a582812..14a41cd 100644 --- a/servers/whatsapp-server/src/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..25706e2 --- /dev/null +++ b/interfaces/whatsapp/whatsapp-env.type.go @@ -0,0 +1,59 @@ +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) GetConfigPath() string { + return sc.configFilePath +} +func (sc *IServerConfig) SetConfigPath(path string) *IServerConfig { + sc.configFilePath = path + return sc +} +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/linux.sh b/linux.sh index bcf6001..ceb05d0 100755 --- a/linux.sh +++ b/linux.sh @@ -1,4 +1,4 @@ -GOOS=linux GOARCH=amd64 go build ./... +GOOS=linux GOARCH=amd64 go build -v ./... GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/http-dump.o" -ldflags="-s -w" -v ./servers/http-dump/dump-server/main.go GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/message-dump.o" -ldflags="-s -w" -v ./servers/http-dump/message-dump/main.go GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/telegram-server.o" -ldflags="-s -w" -v ./servers/telegram-server/main.go @@ -11,4 +11,6 @@ GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/mysql-to-surreal.o" -ldfla GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/main-server.o" -ldflags "-s -w" -v ./servers/jwelly/main-server/main.go GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/mysql-backup.o" -ldflags "-s -w" -v ./servers/jwelly/mysql-backup/main.go GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/mysql-to-mysql.o" -ldflags "-s -w" -v ./servers/jwelly/mysql-to-mysql/main.go -GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/mysql-backup-cmd.o" -v -ldflags="-s -w" ./servers/jwelly/mysql-backup-cmd/main.go \ No newline at end of file +GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/mysql-backup-cmd.o" -v -ldflags="-s -w" ./servers/jwelly/mysql-backup-cmd/main.go +GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/soham/whatsapp-server.o" -v -ldflags="-s -w" ./servers/soham/whatsapp-server/main.go +GOOS=linux GOARCH=amd64 go build -o "dist/linux-amd64/soham/whatsapp-client.o" -v -ldflags="-s -w" ./servers/soham/whatsapp-client/main.go \ No newline at end of file diff --git a/release/soham/whatsapp-client.go b/release/soham/whatsapp-client.go new file mode 100644 index 0000000..5cc3dca --- /dev/null +++ b/release/soham/whatsapp-client.go @@ -0,0 +1,224 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "slices" + "time" +) + +const ( + fileServerURL = "https://files.rpso.in/upload/" + keyValueURL = "https://keyvalue.rpso.in/public/" +) + +var ( + fileServerToken = os.Getenv("FILE_SERVER_TOKEN") + kvToken = os.Getenv("KV_TOKEN") + archs = []string{"amd64", "arm64"} +) + +type VersionInfo struct { + Version int `json:"version"` + URL string `json:"url"` + SHA256 string `json:"sha256"` +} + +func main() { + + version := "" + if len(os.Args) < 2 { + if os.Getenv("VERSION") == "" { + fmt.Println("Usage: builder ") + return + } + version = os.Getenv("VERSION") + } else { + version = os.Args[1] + } + err := os.MkdirAll("build", 0755) + if err != nil { + panic(err) + } + buildFilePath := "./servers/soham/whatsapp-client/main.go" + + if slices.Contains(os.Args, "--dev") { + buildFilePath = "../../servers/soham/whatsapp-client/main.go" + } + + serverBinary := "whatsapp-client.o" + if runtime.GOOS == "windows" { + archs = append(archs, "386") + serverBinary = "whatsapp-client.exe" + } + serverBinaryPath := filepath.Join("build", serverBinary) + for _, arch := range archs { + fmt.Printf("Building server for %s...", arch) + cmd := exec.Command( + "go", + "build", + "-ldflags", + fmt.Sprintf("-s -w -X main.version=%s", version), + "-o", + serverBinaryPath, + buildFilePath, + ) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("GOARCH=%s", arch)) + log.Printf("running command %s", cmd.String()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + panic(err) + } + + fmt.Println("Calculating SHA256...") + + hash, err := sha256File(serverBinaryPath) + if err != nil { + panic(err) + } + // https://files.rpso.in/static/soham/wbot/darwin_amd64/whatsapp-client.o + versionInfo := VersionInfo{ + Version: atoi(version), + URL: fmt.Sprintf("https://files.rpso.in/static/soham/wbot/%s_%s/%s", runtime.GOOS, arch, serverBinary), + SHA256: hash, + } + + data, _ := json.MarshalIndent(versionInfo, "", " ") + + fmt.Println("Uploading server binary...") + err = uploadFile(serverBinaryPath, serverBinary, fmt.Sprintf("soham/wbot/%s_%s", runtime.GOOS, arch)) + // uploadFile(serverBinaryPath, "https://fileserver.com/server_v"+version) + if err != nil { + panic(err) + } + + fmt.Println("Updating keyvalue store...") + + err = updateKeyValue(fmt.Sprintf("soham_go_wbot_%s_%s", runtime.GOOS, arch), data) + if err != nil { + panic(err) + } + // uploadFile(versionFile, "https://kvserver.com/version.json") + } + + fmt.Println("Build and upload complete") +} + +func sha256File(path string) (string, error) { + + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + hash := sha256.New() + + _, err = io.Copy(hash, file) + if err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +func uploadFile(path string, filename string, uploadPath string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + + part, err := writer.CreateFormFile(filename, filepath.Base(path)) + if err != nil { + return err + } + client := &http.Client{ + Timeout: time.Second * 540, + } + io.Copy(part, file) + + writer.WriteField("path", uploadPath) + + writer.Close() + + req, err := http.NewRequest( + "POST", + fileServerURL+filename, + payload, + ) + + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+fileServerToken) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + resp, err := client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + return err + } + fmt.Println(string(body)) + fmt.Println("Uploaded:", filename) + + return nil +} + +func updateKeyValue(key string, data []byte) error { + req, err := http.NewRequest( + "POST", + keyValueURL+key, + bytes.NewBuffer(data), + ) + + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+kvToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + fmt.Println("KeyValue updated:", key) + + return nil +} + +func atoi(s string) int { + + var n int + fmt.Sscanf(s, "%d", &n) + return n +} diff --git a/servers/background-service/configs.json b/servers/background-service/configs.json new file mode 100644 index 0000000..0c0e62d --- /dev/null +++ b/servers/background-service/configs.json @@ -0,0 +1,4 @@ +{ + "servicePath": "", + "args": [] +} diff --git a/servers/background-service/install.bat b/servers/background-service/install.bat new file mode 100644 index 0000000..593b77f --- /dev/null +++ b/servers/background-service/install.bat @@ -0,0 +1,16 @@ +@echo off +rem run this script as admin + +if not exist main.exe ( + echo Build the example before installing by running "go build" + goto :exit +) + +sc create rps-bg-service binpath= "%CD%\main.exe" start= auto DisplayName= "RPS Backgroung Service" +sc description rps-bg-service "Run Services in Backgroung by RPS" +net start rps-bg-service +sc query rps-bg-service + +echo Check rps-bg-service.log + +:exit \ No newline at end of file diff --git a/servers/background-service/main.go b/servers/background-service/main.go new file mode 100644 index 0000000..2d68e7c --- /dev/null +++ b/servers/background-service/main.go @@ -0,0 +1,178 @@ +//go:build windows + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "slices" + "syscall" + "time" + + "github.com/rpsoftech/golang-servers/validator" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/debug" +) + +type ( + tunnelService struct{} + Config struct { + ServicePath string `json:"servicePath" validate:"required"` + Args []string `json:"args"` + } +) + +var ( + ShouldBeRunning = false + BgCommand *exec.Cmd + ThisConfig *Config +) + +func (m *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { + + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue + tick := time.Tick(1 * time.Minute) + + status <- svc.Status{State: svc.StartPending} + + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + StartService() +loop: + for { + select { + case <-tick: + log.Print("Tick Handled...!") + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + log.Print("Service Interrogate.....!", c.CurrentStatus) + status <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + log.Print("Shutting service...!") + StopService() + break loop + case svc.Pause: + log.Print("Service Paused.....!") + StopService() + status <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} + case svc.Continue: + log.Print("Service Continue.....!") + StartService() + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + default: + log.Printf("Unexpected service control request #%d", c) + } + } + } + + status <- svc.Status{State: svc.StopPending} + return false, 1 +} + +func runService(name string, isDebug bool) { + if isDebug { + err := debug.Run(name, &tunnelService{}) + if err != nil { + log.Fatalln("Error running service in debug mode.") + log.Fatalf("Error running service in debug mode. %s", err.Error()) + } + } else { + err := svc.Run(name, &tunnelService{}) + if err != nil { + log.Fatalln("Error running service in Service Control mode.") + } + } +} + +func main() { + if !IsDebug() { + f, err := os.OpenFile(filepath.Join(FindAndReturnCurrentDir(), "debug.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalln(fmt.Errorf("error opening file: %v", err)) + } + defer f.Close() + log.SetOutput(f) + } + start() + runService("tunnelService", IsDebug()) + log.Print("Finished Function") +} + +func start() { + fmt.Println(len(os.Args), os.Args) + currentDir := FindAndReturnCurrentDir() + configFilePAth := filepath.Join(currentDir, "configs.json") + ThisConfig = &Config{} + + fmt.Printf("Config path %s\n", configFilePAth) + if _, err := os.Stat(configFilePAth); errors.Is(err, os.ErrNotExist) { + panic(fmt.Errorf("Config Not Exist on Path %s", configFilePAth)) + } + dat, err := os.ReadFile(configFilePAth) + Check(err) + json.Unmarshal(dat, ThisConfig) + + if errs := validator.Validator.Validate(ThisConfig); len(errs) > 0 { + panic(fmt.Errorf("Config Error %#v", errs)) + } +} + +func StartService() { + ShouldBeRunning = true + go runningService() +} + +func CreateBgService() { + BgCommand = exec.Command(ThisConfig.ServicePath, ThisConfig.Args...) + BgCommand.Stdout = log.Default().Writer() + BgCommand.Stderr = log.Default().Writer() + log.Print("Service Created...!") +} + +func runningService() { + time.Sleep(3 * time.Second) + if ShouldBeRunning { + CreateBgService() + BgCommand.Start() + log.Print("Service Started...!") + BgCommand.Wait() + log.Print("Service Stopped Automatically...!") + BgCommand.Process.Kill() + runningService() + } +} +func StopService() { + ShouldBeRunning = false + BgCommand.Process.Signal(syscall.SIGINT) + BgCommand.Process.Kill() + log.Print("Service Stopped...!") +} + +func IsDebug() bool { + return slices.Contains(os.Args, "--dev") +} + +func FindAndReturnCurrentDir() string { + currentDir := "" + if IsDebug() { + current, err := os.Getwd() + Check(err) + currentDir = current + } else { + exePath, err := os.Executable() + currentDir = filepath.Dir(exePath) + Check(err) + } + return currentDir +} + +func Check(e error) { + if e != nil { + panic(e) + } +} diff --git a/servers/background-service/uninstall.bat b/servers/background-service/uninstall.bat new file mode 100644 index 0000000..becae2f --- /dev/null +++ b/servers/background-service/uninstall.bat @@ -0,0 +1,5 @@ +@echo off +rem run this script as admin + +net stop rps-bg-service +sc delete rps-bg-service \ No newline at end of file diff --git a/servers/soham/common/communication.interface.go b/servers/soham/common/communication.interface.go new file mode 100644 index 0000000..8e2b112 --- /dev/null +++ b/servers/soham/common/communication.interface.go @@ -0,0 +1,59 @@ +package soham_common_req_keys + +type MessageType string + +const ( + STATUS_MESSAGE MessageType = "STATUS_MESSAGE" + REPSONSE_MESSAGE MessageType = "RESPONSE_MESSAGE" + SEND_TEXT_MESSAGE MessageType = "SEND_TEXT_MESSAGE" + SEND_BASE64_IMAGE MessageType = "SEND_BASE64_IMAGE" + SEND_WEB_MEDIA MessageType = "SEND_WEB_MEDIA" + SEND_FILE_PATH_MEDIA MessageType = "SEND_FILE_PATH_MEDIA" +) + +type WhatsappClientMessage struct { + ReqId string `json:"reqid"` + Type MessageType `json:"type"` + Message any `json:"message"` +} + +type ApiSendRequestBase struct { + From int `json:"from" validate:"required"` + To []string `json:"to" validate:"required"` + Wait int `json:"wait"` +} +type SendTextMessage struct { + ApiSendRequestBase + Message string `json:"message" validate:"required"` +} + +type SendBase64Image struct { + ApiSendRequestBase + Base64 string `json:"base64" validate:"required"` + // Ext string `json:"ext_name" validate:"required,regexp=^.[\\w]*$"` + Media string `json:"media_name" validate:"required"` + ImageDesc string `json:"image_desc"` +} + +type SendWebMediaType struct { + ApiSendRequestBase + WebMediaLink string `json:"web_media_link" validate:"required,url"` + MediaName string `json:"media_name" validate:"required"` + ImageDesc string `json:"image_desc"` +} + +type SendFilePathMediaType struct { + ApiSendRequestBase + LocalMediaPath string `json:"local_media_path" validate:"required"` + ImageDesc string `json:"image_desc"` + // { + // key: 'local_media_path', + // required: true, + // type: 'string', + // }, + // { + // key: 'image_desc', + // required: false, + // type: 'string', + // }, +} diff --git a/servers/soham/common/connection.status.go b/servers/soham/common/connection.status.go new file mode 100644 index 0000000..f2daf2e --- /dev/null +++ b/servers/soham/common/connection.status.go @@ -0,0 +1,59 @@ +package soham_common_req_keys + +import ( + "github.com/rpsoftech/golang-servers/interfaces" + "github.com/rpsoftech/golang-servers/validator" +) + +type ConnectionStatus string + +const ( + DISCONNECTED ConnectionStatus = "0" + NOT_LOGGED_IN ConnectionStatus = "-1" + LOGGED_IN ConnectionStatus = "1" +) + +var ( + connectionStatusMap = interfaces.EnumValidatorBase{ + Data: map[string]any{ + "0": DISCONNECTED, + "-1": NOT_LOGGED_IN, + "1": LOGGED_IN, + }, + } +) + +func init() { + validator.RegisterEnumValidatorFunc("ConnectionStatusEnum", connectionStatusMap.Validate) +} +func StringToEnumConnectionStatus(value string) (ConnectionStatus, bool) { + switch value { + case "0": + return DISCONNECTED, true + case "-1": + return NOT_LOGGED_IN, true + case "1": + return LOGGED_IN, true + default: + return DISCONNECTED, false + } +} + +func (s ConnectionStatus) String() string { + switch s { + case DISCONNECTED: + return "0" + case NOT_LOGGED_IN: + return "-1" + case LOGGED_IN: + return "1" + } + return "0" +} +func (s ConnectionStatus) IsValid() bool { + switch s { + case DISCONNECTED, NOT_LOGGED_IN, LOGGED_IN: + return true + } + return false +} diff --git a/servers/soham/common/req-token.type.go b/servers/soham/common/req-token.type.go new file mode 100644 index 0000000..92013bd --- /dev/null +++ b/servers/soham/common/req-token.type.go @@ -0,0 +1,8 @@ +package soham_common_req_keys + +import "github.com/golang-jwt/jwt/v5" + +type ReqTokenType struct { + *jwt.RegisteredClaims + From int `json:"from" validate:"required"` +} diff --git a/servers/soham/common/req.keys.go b/servers/soham/common/req.keys.go new file mode 100644 index 0000000..bc34824 --- /dev/null +++ b/servers/soham/common/req.keys.go @@ -0,0 +1,13 @@ +package soham_common_req_keys + +const ( + WHATSAPP_CLIENT_TOKEN_KEY = "UUID_AUTH" + WHATSAPP_CLIENT_NUM_KEY = "NUMBER_AUTH" +) +const ( + ERROR_INVALID_UUID_TOKEN = 203 + ERROR_NUMBER_TOKEN_NOT_PASSED = 204 + ERROR_GENERATE_TOKEN_MISMATCH = 205 + ERROR_MISMATCH_NUMBER_FROM_TOKEN = 206 + ERROR_INVALID_WEB_MEDIA_LINK = 207 +) diff --git a/servers/soham/common/valiadte-number-from-req.go b/servers/soham/common/valiadte-number-from-req.go new file mode 100644 index 0000000..383206d --- /dev/null +++ b/servers/soham/common/valiadte-number-from-req.go @@ -0,0 +1,23 @@ +package soham_common_req_keys + +import ( + "fmt" + "net/http" + + "github.com/gofiber/fiber/v3" + "github.com/rpsoftech/golang-servers/interfaces" +) + +func ValidateNumberMatchingInToken(c fiber.Ctx, number int) error { + id, ok := c.Locals(WHATSAPP_CLIENT_NUM_KEY).(int) + if !ok || number != id { + return &interfaces.RequestError{ + StatusCode: http.StatusForbidden, + Code: ERROR_MISMATCH_NUMBER_FROM_TOKEN, + Message: "Your can not access this resource due to different number", + Name: "ERROR_MISMATCH_NUMBER_FROM_TOKEN", + Extra: fmt.Sprintf("Expected := %d Got := %d", id, number), + } + } + return nil +} 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/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 new file mode 100644 index 0000000..3be2424 --- /dev/null +++ b/servers/soham/whatsapp-client/core/qr-browser.api.go @@ -0,0 +1,70 @@ +package whatsapp_client_core + +import ( + "fmt" + + "github.com/gofiber/fiber/v2" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" +) + +const index = ` + + + + + QR Code Scanner For + + +

Scan The QR Code to Log into whatsapp web account

+

Number: %s

+ + + +` + +func OpenBrowserWithQr(c *fiber.Ctx) error { + id := c.Params("id") + number, ok := whatsapp_config.WhatsappNumberToIDMap[id] + if !ok { + return c.Status(404).SendString("Invalid ID") + } + c.Set("Content-Type", "text/html; charset=utf-8") + return c.Send([]byte(fmt.Sprintf(index, number, id))) +} diff --git a/servers/soham/whatsapp-client/env/index.go b/servers/soham/whatsapp-client/env/index.go new file mode 100644 index 0000000..c78d23b --- /dev/null +++ b/servers/soham/whatsapp-client/env/index.go @@ -0,0 +1,27 @@ +package soham_whatsapp_client_env + +import ( + "net/url" + "path/filepath" + + "github.com/rpsoftech/golang-servers/env" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" +) + +const SOCKET_URL_KEY = "SOCKET_URL" + +var SocketUrl *url.URL + +func InialiseSohamWhatsappClientEnv() { + env.LoadEnv(filepath.Join(env.FindAndReturnCurrentDir(), "whatsapp-client.env")) + whatsapp_config.InitaliseWhatsappEnvAndConfig() + urlString := env.Env.GetEnv(SOCKET_URL_KEY) + if urlString == "" { + panic("SOCKET_URL not defined in env file") + } + urlObject, err := url.ParseRequestURI(urlString) + if err != nil { + panic("SOCKET_URL is not a valid URL") + } + SocketUrl = urlObject +} diff --git a/servers/soham/whatsapp-client/main.go b/servers/soham/whatsapp-client/main.go new file mode 100644 index 0000000..f5662a2 --- /dev/null +++ b/servers/soham/whatsapp-client/main.go @@ -0,0 +1,95 @@ +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" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" + whatsapp_core "github.com/rpsoftech/golang-servers/functions/whatsapp/core" + whatsapp_middleware "github.com/rpsoftech/golang-servers/functions/whatsapp/middleware" + "github.com/rpsoftech/golang-servers/interfaces" + soham_whatsapp_client_apis "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-client/apis" + whatsapp_client_core "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-client/core" + soham_whatsapp_client_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-client/env" + soham_whatsapp_client_websocket "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-client/websoceket" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +var version string + +func main() { + soham_whatsapp_client_env.InialiseSohamWhatsappClientEnv() + 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_core.OutPutFilePath = ReturnOutPutFilePath(env.FindAndReturnCurrentDir()) + container := whatsapp_core.InitSqlContainer() + if whatsapp_config.Env.AUTO_CONNECT_TO_WHATSAPP { + for k, n := range whatsapp_config.WhatsappNumberConfigMap.Tokens { + jidString := whatsapp_config.WhatsappNumberConfigMap.JID[k] + whatsapp_config.WhatsappNumberToIDMap[k] = n + go whatsapp_core.ConnectToNumber(jidString, k, container) + websokcetObj := soham_whatsapp_client_websocket.WebsocketConnectionObject{ + Url: soham_whatsapp_client_env.SocketUrl, + Conn: nil, + UUID: k, + NUMBER: n, + WhatsappConnectionMap: &whatsapp_core.ConnectionMap, + } + go websokcetObj.InitalizeWebsocket() + go websokcetObj.CheckStatusAndSendResponse() + } + } + + 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) + soham_whatsapp_client_apis.AddApis(app.Group("/v1", whatsapp_middleware.TokenDecrypter, whatsapp_middleware.AllowOnlyValidTokenMiddleWare)) + + 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/soham/whatsapp-client/websoceket/index.go b/servers/soham/whatsapp-client/websoceket/index.go new file mode 100644 index 0000000..ae2120a --- /dev/null +++ b/servers/soham/whatsapp-client/websoceket/index.go @@ -0,0 +1,352 @@ +package soham_whatsapp_client_websocket + +import ( + "encoding/json" + "log" + "net/http" + "net/url" + "path" + "path/filepath" + "time" + + "github.com/gorilla/websocket" + "github.com/rpsoftech/golang-servers/env" + whatsapp_core "github.com/rpsoftech/golang-servers/functions/whatsapp/core" + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +type WebsocketConnectionObject struct { + Conn *websocket.Conn + Url *url.URL + WhatsappConnectionStatus soham_common_req_keys.ConnectionStatus + connectionCalled bool + connected bool + UUID string + NUMBER string + WhatsappConnectionMap *whatsapp_core.IWhatsappConnectionMap + WhatsappConnection *whatsapp_core.WhatsappConnection +} + +// var connectionCalled = false + +func (w *WebsocketConnectionObject) InitalizeWebsocket() { + if w.connectionCalled { + return + } + w.connectionCalled = true + w.connected = false + con, _, err := websocket.DefaultDialer.Dial(w.Url.String(), http.Header{ + soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY: []string{w.NUMBER}, + soham_common_req_keys.WHATSAPP_CLIENT_TOKEN_KEY: []string{w.UUID}, + }) + w.Conn = con + defer con.Close() + if err != nil { + // log connection error + log.Fatal("dial:", err) + } + w.connected = true + done := make(chan int) + go func() { + for { + // Read messages from the connection + _, msg, err := w.Conn.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + var whatsappClientMessage soham_common_req_keys.WhatsappClientMessage + err = json.Unmarshal(msg, &whatsappClientMessage) + if err != nil { + log.Println("Error unmarshalling message:", err) + continue + } + if whatsappClientMessage.Type == soham_common_req_keys.SEND_TEXT_MESSAGE { + go w.SendTextMessage(&whatsappClientMessage) + continue + } + if whatsappClientMessage.Type == soham_common_req_keys.SEND_BASE64_IMAGE { + go w.SendBase64Image(&whatsappClientMessage) + continue + } + if whatsappClientMessage.Type == soham_common_req_keys.SEND_WEB_MEDIA { + go w.SendWebMedia(&whatsappClientMessage) + continue + } + if whatsappClientMessage.Type == soham_common_req_keys.SEND_FILE_PATH_MEDIA { + go w.SendMediaWithFilePath(&whatsappClientMessage) + continue + } + } + done <- 1 + }() + <-done + w.connected = false + w.connectionCalled = false + go func() { + time.Sleep(time.Second * 10) + done <- 1 + }() + w.WhatsappConnectionStatus = soham_common_req_keys.DISCONNECTED + <-done + w.InitalizeWebsocket() +} + +func (w *WebsocketConnectionObject) SendTextMessage(wcm *soham_common_req_keys.WhatsappClientMessage) { + reqId := wcm.ReqId + jsonData, err := json.Marshal(wcm.Message) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + body := new(soham_common_req_keys.SendTextMessage) + err = json.Unmarshal(jsonData, body) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + if w.WhatsappConnection == nil { + wc, ok := (*w.WhatsappConnectionMap)[w.UUID] + if !ok { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid from number"}, + }) + return + } + w.WhatsappConnection = wc + } + wc := w.WhatsappConnection + if wc.ConnectionStatus != 1 { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Whatsapp not connected"}, + }) + return + } + + resp := wc.SendTextMessage(body.To, body.Message) + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: resp, + }) +} + +func (w *WebsocketConnectionObject) SendBase64Image(wcm *soham_common_req_keys.WhatsappClientMessage) { + reqId := wcm.ReqId + jsonData, err := json.Marshal(wcm.Message) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + body := new(soham_common_req_keys.SendBase64Image) + err = json.Unmarshal(jsonData, body) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + if w.WhatsappConnection == nil { + wc, ok := (*w.WhatsappConnectionMap)[w.UUID] + if !ok { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid from number"}, + }) + return + } + w.WhatsappConnection = wc + } + wc := w.WhatsappConnection + if wc.ConnectionStatus != 1 { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Whatsapp not connected"}, + }) + return + } + + resp := wc.SendMediaFileBase64(body.To, body.Base64, body.Media, body.ImageDesc) + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: resp, + }) +} + +func (w *WebsocketConnectionObject) SendWebMedia(wcm *soham_common_req_keys.WhatsappClientMessage) { + reqId := wcm.ReqId + jsonData, err := json.Marshal(wcm.Message) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + body := new(soham_common_req_keys.SendWebMediaType) + err = json.Unmarshal(jsonData, body) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type", Extra: err}, + }) + return + } + filePath := path.Join(env.FindAndReturnCurrentDir(), "tmp", body.MediaName) + err = utility_functions.DownloadFile(filePath, body.WebMediaLink) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Failed to download media file", Extra: err}, + }) + return + } + if w.WhatsappConnection == nil { + wc, ok := (*w.WhatsappConnectionMap)[w.UUID] + if !ok { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid from number"}, + }) + return + } + w.WhatsappConnection = wc + } + wc := w.WhatsappConnection + if wc.ConnectionStatus != 1 { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Whatsapp not connected"}, + }) + return + } + + resp := wc.SendMediaFileWithPath(body.To, filePath, body.MediaName, body.ImageDesc) + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: resp, + }) +} +func (w *WebsocketConnectionObject) SendMediaWithFilePath(wcm *soham_common_req_keys.WhatsappClientMessage) { + reqId := wcm.ReqId + jsonData, err := json.Marshal(wcm.Message) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + body := new(soham_common_req_keys.SendFilePathMediaType) + err = json.Unmarshal(jsonData, body) + if err != nil { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid message type"}, + }) + return + } + if w.WhatsappConnection == nil { + wc, ok := (*w.WhatsappConnectionMap)[w.UUID] + if !ok { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid from number"}, + }) + return + } + w.WhatsappConnection = wc + } + wc := w.WhatsappConnection + if wc.ConnectionStatus != 1 { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Whatsapp not connected"}, + }) + return + } + + resp := wc.SendMediaFileWithPath(body.To, body.LocalMediaPath, filepath.Base(body.LocalMediaPath), body.ImageDesc) + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqId, + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: resp, + }) +} + +func (w *WebsocketConnectionObject) SendResponse(wcm *soham_common_req_keys.WhatsappClientMessage) bool { + if !w.connected { + return false + } + w.Conn.WriteJSON(wcm) + return true +} + +func (w *WebsocketConnectionObject) CheckStatusAndSendResponse() { + time.Sleep(time.Second * 5) + if w.WhatsappConnectionStatus == "" { + w.WhatsappConnectionStatus = soham_common_req_keys.NOT_LOGGED_IN + } + if w.WhatsappConnection == nil { + wc, ok := (*w.WhatsappConnectionMap)[w.UUID] + if !ok { + w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + Type: soham_common_req_keys.REPSONSE_MESSAGE, + Message: interfaces.RequestError{Message: "Invalid from number"}, + }) + } + w.WhatsappConnection = wc + } + wc := w.WhatsappConnection + if wc.ConnectionStatus == 1 && w.WhatsappConnectionStatus != soham_common_req_keys.LOGGED_IN { + if w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + Type: soham_common_req_keys.STATUS_MESSAGE, + Message: soham_common_req_keys.LOGGED_IN, + }) { + w.WhatsappConnectionStatus = soham_common_req_keys.LOGGED_IN + } + } + if wc.ConnectionStatus != 1 && w.WhatsappConnectionStatus != soham_common_req_keys.LOGGED_IN { + if w.SendResponse(&soham_common_req_keys.WhatsappClientMessage{ + Type: soham_common_req_keys.STATUS_MESSAGE, + Message: soham_common_req_keys.NOT_LOGGED_IN, + }) { + w.WhatsappConnectionStatus = soham_common_req_keys.NOT_LOGGED_IN + } + } + time.Sleep(time.Second * 5) + w.CheckStatusAndSendResponse() +} diff --git a/servers/soham/whatsapp-server/.gitignore b/servers/soham/whatsapp-server/.gitignore new file mode 100644 index 0000000..0b0acb4 --- /dev/null +++ b/servers/soham/whatsapp-server/.gitignore @@ -0,0 +1 @@ +ssl.config.json \ No newline at end of file diff --git a/servers/soham/whatsapp-server/api/index.go b/servers/soham/whatsapp-server/api/index.go new file mode 100644 index 0000000..155f8fb --- /dev/null +++ b/servers/soham/whatsapp-server/api/index.go @@ -0,0 +1,16 @@ +package soham_whatsapp_server_api + +import ( + "github.com/gofiber/fiber/v3" + soham_whatsapp_server_middleware "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/middleware" +) + +func AddApis(router fiber.Router) { + router.Use(soham_whatsapp_server_middleware.TokenDecrypter, + soham_whatsapp_server_middleware.AllowOnlyValidTokenMiddleWare, + soham_whatsapp_server_middleware.AllowOnlyValidLoggedInWhatsapp) + router.Post("/send_message", SendMessageApi) + router.Post("/send_base64_media", SendBase64ImageApi) + router.Post("/send_web_media", SendWebMediaApi) + router.Post("/send_local_media", SendLocalMediaApi) +} diff --git a/servers/soham/whatsapp-server/api/send-base64-image.go b/servers/soham/whatsapp-server/api/send-base64-image.go new file mode 100644 index 0000000..97c5b4e --- /dev/null +++ b/servers/soham/whatsapp-server/api/send-base64-image.go @@ -0,0 +1,27 @@ +package soham_whatsapp_server_api + +import ( + "github.com/gofiber/fiber/v3" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +func SendBase64ImageApi(c fiber.Ctx) error { + body := new(soham_common_req_keys.SendBase64Image) + // c.BodyParser(body) + if err := c.Bind().Body(body); err != nil { + return err + } + if err := utility_functions.ValidateReqInput(body); err != nil { + return err + } + if err := soham_common_req_keys.ValidateNumberMatchingInToken(c, body.From); err != nil { + return err + } + resp, err := soham_whatsapp_server_services.WhatsappService.SendBase64Image(body) + if err != nil { + return err + } + return c.JSON(resp) +} diff --git a/servers/soham/whatsapp-server/api/send-local-media.go b/servers/soham/whatsapp-server/api/send-local-media.go new file mode 100644 index 0000000..df30799 --- /dev/null +++ b/servers/soham/whatsapp-server/api/send-local-media.go @@ -0,0 +1,27 @@ +package soham_whatsapp_server_api + +import ( + "github.com/gofiber/fiber/v3" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +func SendLocalMediaApi(c fiber.Ctx) error { + body := new(soham_common_req_keys.SendFilePathMediaType) + // c.BodyParser(body) + if err := c.Bind().Body(body); err != nil { + return err + } + if err := utility_functions.ValidateReqInput(body); err != nil { + return err + } + if err := soham_common_req_keys.ValidateNumberMatchingInToken(c, body.From); err != nil { + return err + } + resp, err := soham_whatsapp_server_services.WhatsappService.SendLocalMedia(body) + if err != nil { + return err + } + return c.JSON(resp) +} diff --git a/servers/soham/whatsapp-server/api/send-message.api.go b/servers/soham/whatsapp-server/api/send-message.api.go new file mode 100644 index 0000000..b773706 --- /dev/null +++ b/servers/soham/whatsapp-server/api/send-message.api.go @@ -0,0 +1,27 @@ +package soham_whatsapp_server_api + +import ( + "github.com/gofiber/fiber/v3" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +func SendMessageApi(c fiber.Ctx) error { + body := new(soham_common_req_keys.SendTextMessage) + // c.BodyParser(body) + if err := c.Bind().Body(body); err != nil { + return err + } + if err := utility_functions.ValidateReqInput(body); err != nil { + return err + } + if err := soham_common_req_keys.ValidateNumberMatchingInToken(c, body.From); err != nil { + return err + } + resp, err := soham_whatsapp_server_services.WhatsappService.SendTextMessage(body) + if err != nil { + return err + } + return c.JSON(resp) +} diff --git a/servers/soham/whatsapp-server/api/send-web-media.go b/servers/soham/whatsapp-server/api/send-web-media.go new file mode 100644 index 0000000..2f52749 --- /dev/null +++ b/servers/soham/whatsapp-server/api/send-web-media.go @@ -0,0 +1,36 @@ +package soham_whatsapp_server_api + +import ( + "github.com/gofiber/fiber/v3" + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +func SendWebMediaApi(c fiber.Ctx) error { + body := new(soham_common_req_keys.SendWebMediaType) + // c.BodyParser(body) + if err := c.Bind().Body(body); err != nil { + return err + } + if err := utility_functions.ValidateReqInput(body); err != nil { + return err + } + if err := soham_common_req_keys.ValidateNumberMatchingInToken(c, body.From); err != nil { + return err + } + if !utility_functions.ValidateUrl(body.WebMediaLink) { + return &interfaces.RequestError{ + StatusCode: fiber.StatusBadRequest, + Code: soham_common_req_keys.ERROR_INVALID_WEB_MEDIA_LINK, + Message: "The web media link provided is not a valid url", + Name: "ERROR_INVALID_WEB_MEDIA_LINK", + } + } + resp, err := soham_whatsapp_server_services.WhatsappService.SendWebMedia(body) + if err != nil { + return err + } + return c.JSON(resp) +} diff --git a/servers/soham/whatsapp-server/env/index.go b/servers/soham/whatsapp-server/env/index.go new file mode 100644 index 0000000..73b9b77 --- /dev/null +++ b/servers/soham/whatsapp-server/env/index.go @@ -0,0 +1,53 @@ +package soham_whatsapp_server_env + +import ( + "github.com/gofiber/contrib/v3/websocket" + "github.com/google/uuid" + "github.com/rpsoftech/golang-servers/env" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" +) + +type ( + whatsappEnv struct { + DefaultEnv *env.DefaultEnvInterface + BASE_UUID string `json:"BASE_UUID" validate:"required"` + ACCESS_TOKEN_KEY string `json:"ACCESS_TOKEN_KEY" validate:"required,min=10"` + UUIDObj uuid.UUID `json:"-" validate:"-"` + } + WebsocketConnection struct { + Conn *websocket.Conn + Status soham_common_req_keys.ConnectionStatus + } +) + +var Env *whatsappEnv + +var ( + WebsocketConnectionMap = make(map[int]*WebsocketConnection) + ConnectionNumberStatusMap = make(map[int]soham_common_req_keys.ConnectionStatus) + ReqestIdMap = make(map[string]chan any) +) + +func init() { + env.LoadEnv("whatsapp-server.env") + println("WhatsApp ServerEnv Initialized") + Env = &whatsappEnv{ + DefaultEnv: env.Env, + BASE_UUID: env.Env.GetEnv("BASE_UUID"), + ACCESS_TOKEN_KEY: env.Env.GetEnv("ACCESS_TOKEN_KEY"), + } + uuidObj, err := uuid.Parse(Env.BASE_UUID) + if err != nil { + panic("Invalid BASE_UUID in env file") + } + Env.UUIDObj = uuidObj + env.ValidateEnv(Env) +} + +func (c *WebsocketConnection) SendMessage(reqid string, messageType soham_common_req_keys.MessageType, s any) error { + return c.Conn.WriteJSON(soham_common_req_keys.WhatsappClientMessage{ + ReqId: reqid, + Type: messageType, + Message: s, + }) +} diff --git a/servers/soham/whatsapp-server/main.go b/servers/soham/whatsapp-server/main.go new file mode 100644 index 0000000..95c3a58 --- /dev/null +++ b/servers/soham/whatsapp-server/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/gofiber/contrib/v3/websocket" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/logger" + "github.com/rpsoftech/golang-servers/env" + "github.com/rpsoftech/golang-servers/interfaces" + soham_whatsapp_server_api "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/api" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" + soham_whatsapp_server_middleware "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/middleware" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + soham_whatsapp_server_websocket "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/websocket" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" + "github.com/rpsoftech/golang-servers/validator" +) + +func main() { + + soham_whatsapp_server_services.InialiseWhatsappService(&soham_whatsapp_server_env.ReqestIdMap, + &soham_whatsapp_server_env.WebsocketConnectionMap) + + 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.Use("/whatsapp-client", soham_whatsapp_server_middleware.ValidateWhatsAppClientToken) + app.Get("/whatsapp-client/ws", websocket.New(soham_whatsapp_server_websocket.WhatsappClientWebsocketHandler)) + soham_whatsapp_server_api.AddApis(app.Group("/api")) + 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) + tlsConfig := fiber.ListenConfig{ + // TLSConfig: , + // TLSMinVersion: tls.VersionTLS10, + } + + sslPath := filepath.Join(env.FindAndReturnCurrentDir(), "ssl.config.json") + if _, err := utility_functions.Exist(sslPath); err == nil { + sslConfig := new(interfaces.SSLConfig) + dat, err := os.ReadFile(sslPath) + env.Check(err) + err = json.Unmarshal(dat, sslConfig) + env.Check(err) + if errs := validator.Validator.Validate(sslConfig); len(errs) > 0 { + panic(fmt.Errorf("SSL_CONFIG_ERROR %#v", errs)) + } + if _, err := os.Stat(sslConfig.CertFilePath); os.IsNotExist(err) { + log.Printf("SSL Cert File Not Exist at %s", sslConfig.CertFilePath) + return + } + if _, err := os.Stat(sslConfig.KeyFilePath); os.IsNotExist(err) { + log.Printf("SSL Key File Not Exist at %s", sslConfig.KeyFilePath) + return + } + certificate, err := tls.LoadX509KeyPair(sslConfig.CertFilePath, sslConfig.KeyFilePath) + if err != nil { + log.Printf("Error Loading SSL Certificate, Error: %v", err) + return + } + caCertPool := x509.NewCertPool() + caCert, err := os.ReadFile("/Users/keyurshah/Projects/GolangServers/ssl/fullchain.crt") + if err != nil { + log.Printf("Error Reading CA Certificate File, Error: %v", err) + return + } + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.CertClientFile = sslConfig.CertFilePath + tlsConfig.TLSConfig = &tls.Config{ + RootCAs: caCertPool, + Certificates: []tls.Certificate{certificate}, + } + // tlsConfig.TLSConfig.Certificates = + // key := sslConfig.KeyFilePath + } else { + log.Printf("SSL File Path Not Exist at %s", sslPath) + } + app.Listen(hostAndPort, tlsConfig) +} diff --git a/servers/soham/whatsapp-server/middleware/token-validator.go b/servers/soham/whatsapp-server/middleware/token-validator.go new file mode 100644 index 0000000..298e349 --- /dev/null +++ b/servers/soham/whatsapp-server/middleware/token-validator.go @@ -0,0 +1,109 @@ +package soham_whatsapp_server_middleware + +import ( + "fmt" + "strings" + + "github.com/gofiber/fiber/v3" + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" + soham_whatsapp_server_services "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/services" + "github.com/rpsoftech/golang-servers/utility/jwt" +) + +var accessTokenService *jwt.TokenService + +func init() { + accessTokenService = soham_whatsapp_server_services.GetAccessTokenService() +} + +// fiber middleware for jwt +func TokenDecrypter(c fiber.Ctx) error { + // reqHeaders := + tokenString := c.Get("Authorization") + if tokenString == "" { + 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() + } + splitToken := strings.Split(tokenString, " ") + if len(splitToken) != 2 { + 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() + } + claim, err := soham_whatsapp_server_services.ValidateUserRequestToken(accessTokenService, &splitToken[1]) + if err != nil { + c.Locals(interfaces.REQ_LOCAL_ERROR_KEY, err) + return c.Next() + } + c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY, claim.From) + return c.Next() +} + +func AllowOnlyValidTokenMiddleWare(c fiber.Ctx) error { + jwtRawFromLocal := c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_NUM_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(soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY) + token, ok := jwtRawFromLocal.(int) + if !ok { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "User Token Not Found", + Name: "ERROR_INVALID_TOKEN", + } + } + if value, ok := soham_whatsapp_server_env.ConnectionNumberStatusMap[token]; !ok { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "Invalid Token", + Name: "ERROR_INVALID_TOKEN", + } + } else if value != soham_common_req_keys.LOGGED_IN { + return &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_CONNECTION_LOGGED_OUT, + Message: fmt.Sprintf("Connection with number %d is not Loggedin Or Connection offline status is %d", token, value), + Name: "ERROR_CONNECTION_LOGGED_OUT", + Extra: fiber.Map{ + "status": value, + }, + } + } + return c.Next() +} diff --git a/servers/soham/whatsapp-server/middleware/whatsapp-client.validate.go b/servers/soham/whatsapp-server/middleware/whatsapp-client.validate.go new file mode 100644 index 0000000..f0c9ead --- /dev/null +++ b/servers/soham/whatsapp-server/middleware/whatsapp-client.validate.go @@ -0,0 +1,42 @@ +package soham_whatsapp_server_middleware + +import ( + "github.com/gofiber/fiber/v3" + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +func ValidateWhatsAppClientToken(c fiber.Ctx) error { + uuidToken := c.Get(soham_common_req_keys.WHATSAPP_CLIENT_TOKEN_KEY) + if uuidToken == "" { + return &interfaces.RequestError{ + StatusCode: 403, + Code: soham_common_req_keys.ERROR_INVALID_UUID_TOKEN, + Message: "Please Pass Valid UUID Token", + Name: "ERROR_TOKEN_NOT_BEFORE", + } + } + numberToken := c.Get(soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY) + if numberToken == "" { + return &interfaces.RequestError{ + StatusCode: 403, + Code: soham_common_req_keys.ERROR_NUMBER_TOKEN_NOT_PASSED, + Message: "Please Pass Valid Number Token", + Name: "ERROR_NUMBER_TOKEN_NOT_PASSED", + } + } + generateToken := utility_functions.UUIDv5(soham_whatsapp_server_env.Env.UUIDObj, numberToken) + if generateToken != uuidToken { + return &interfaces.RequestError{ + StatusCode: 403, + Code: soham_common_req_keys.ERROR_GENERATE_TOKEN_MISMATCH, + Message: "Please PASS VALID UUID AND NUMBER TOKEN", + Name: "ERROR_GENERATE_TOKEN_MISMATCH", + } + } + c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY, numberToken) + c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_TOKEN_KEY, uuidToken) + return c.Next() +} diff --git a/servers/soham/whatsapp-server/services/access-refresh-token.service.go b/servers/soham/whatsapp-server/services/access-refresh-token.service.go new file mode 100644 index 0000000..198358c --- /dev/null +++ b/servers/soham/whatsapp-server/services/access-refresh-token.service.go @@ -0,0 +1,42 @@ +package soham_whatsapp_server_services + +import ( + jwtv5 "github.com/golang-jwt/jwt/v5" + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" + "github.com/rpsoftech/golang-servers/utility/jwt" +) + +var AccessTokenService *jwt.TokenService +var RefreshTokenService *jwt.TokenService + +func GetAccessTokenService() *jwt.TokenService { + if AccessTokenService == nil { + AccessTokenService = &jwt.TokenService{ + SigningKey: []byte(soham_whatsapp_server_env.Env.ACCESS_TOKEN_KEY), + Parser: jwtv5.NewParser(jwtv5.WithValidMethods([]string{"HS256"})), + } + println("Token Service Initialized") + } + return AccessTokenService +} + +func ValidateUserRequestToken(t *jwt.TokenService, token *string) (*soham_common_req_keys.ReqTokenType, error) { + claimRaw, err := t.Parser.ParseWithClaims(*token, &soham_common_req_keys.ReqTokenType{}, t.Keyfunc) + if err != nil { + return nil, err + } + claim, ok := claimRaw.Claims.(*soham_common_req_keys.ReqTokenType) + if !ok { + err = &interfaces.RequestError{ + StatusCode: 401, + Code: interfaces.ERROR_INVALID_TOKEN, + Message: "Error InValid Token Body", + Name: "ERROR_INVALID_TOKEN_BODY", + Extra: err, + } + return nil, err + } + return claim, nil +} diff --git a/servers/soham/whatsapp-server/services/whatsapp.serivce.go b/servers/soham/whatsapp-server/services/whatsapp.serivce.go new file mode 100644 index 0000000..8896591 --- /dev/null +++ b/servers/soham/whatsapp-server/services/whatsapp.serivce.go @@ -0,0 +1,61 @@ +package soham_whatsapp_server_services + +import ( + "log" + "net/http" + + "github.com/rpsoftech/golang-servers/interfaces" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" + utility_functions "github.com/rpsoftech/golang-servers/utility/functions" +) + +type whatsappService struct { + ReqIdMap *map[string]chan any + WebsocketConnectionMap *map[int]*soham_whatsapp_server_env.WebsocketConnection +} + +var WhatsappService *whatsappService + +func InialiseWhatsappService(reqIdMap *map[string]chan any, websocketConnectionMap *map[int]*soham_whatsapp_server_env.WebsocketConnection) *whatsappService { + WhatsappService = &whatsappService{ + ReqIdMap: reqIdMap, + WebsocketConnectionMap: websocketConnectionMap, + } + log.Println("Whatsapp Service Intialised") + return WhatsappService +} + +func (w *whatsappService) SendTextMessage(s *soham_common_req_keys.SendTextMessage) (any, error) { + return w.sendMessage(soham_common_req_keys.SEND_TEXT_MESSAGE, s.From, s) +} + +func (w *whatsappService) SendBase64Image(s *soham_common_req_keys.SendBase64Image) (any, error) { + return w.sendMessage(soham_common_req_keys.SEND_BASE64_IMAGE, s.From, s) +} +func (w *whatsappService) SendWebMedia(s *soham_common_req_keys.SendWebMediaType) (any, error) { + return w.sendMessage(soham_common_req_keys.SEND_WEB_MEDIA, s.From, s) +} +func (w *whatsappService) SendLocalMedia(s *soham_common_req_keys.SendFilePathMediaType) (any, error) { + return w.sendMessage(soham_common_req_keys.SEND_FILE_PATH_MEDIA, s.From, s) +} + +func (w *whatsappService) sendMessage(messageType soham_common_req_keys.MessageType, from int, s any) (any, error) { + conn, ok := (*w.WebsocketConnectionMap)[from] + if !ok { + return nil, &interfaces.RequestError{ + StatusCode: http.StatusNotFound, + Code: soham_common_req_keys.ERROR_MISMATCH_NUMBER_FROM_TOKEN, + Message: "Number not found", + Name: "ERROR_NUMBER_NOT_FOUND", + } + } + reqId := utility_functions.GenerateNewUUID() + (*w.ReqIdMap)[reqId] = make(chan any) + defer delete((*w.ReqIdMap), reqId) + err := conn.SendMessage(reqId, messageType, s) + if err != nil { + return nil, err + } + return <-(*w.ReqIdMap)[reqId], nil +} diff --git a/servers/soham/whatsapp-server/websocket/index.go b/servers/soham/whatsapp-server/websocket/index.go new file mode 100644 index 0000000..9335b89 --- /dev/null +++ b/servers/soham/whatsapp-server/websocket/index.go @@ -0,0 +1,82 @@ +package soham_whatsapp_server_websocket + +import ( + "encoding/json" + "log" + "strconv" + + "github.com/gofiber/contrib/v3/websocket" + soham_common_req_keys "github.com/rpsoftech/golang-servers/servers/soham/common" + soham_whatsapp_server_env "github.com/rpsoftech/golang-servers/servers/soham/whatsapp-server/env" +) + +func WhatsappClientWebsocketHandler(c *websocket.Conn) { + numberTokenString, ok := c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_NUM_KEY).(string) + if !ok { + log.Println("Missing Number Token in WebSocket connection") + c.Close() + return + } + uuidToken, ok := c.Locals(soham_common_req_keys.WHATSAPP_CLIENT_TOKEN_KEY).(string) + if !ok { + log.Println("Missing UUID Token in WebSocket connection") + c.Close() + return + } + println("Websocket Connection Established with Number Token:", uuidToken) + log.Printf("Connected ======> %s\n", numberTokenString) + numberToken, err := strconv.Atoi(numberTokenString) + if err != nil { + log.Println("Error converting number token to integer:", err) + c.Close() + return + } + connection := &soham_whatsapp_server_env.WebsocketConnection{ + Conn: c, + Status: soham_common_req_keys.NOT_LOGGED_IN, + } + + soham_whatsapp_server_env.WebsocketConnectionMap[numberToken] = connection + soham_whatsapp_server_env.ConnectionNumberStatusMap[numberToken] = soham_common_req_keys.NOT_LOGGED_IN + log.Printf("Subscribed ======> %d\n", numberToken) + log.Printf("ConnectionStatus ======> %s\n", soham_common_req_keys.NOT_LOGGED_IN) + for { + _, msg, err := c.ReadMessage() + if err != nil { + break + } + var whatsappClientMessage soham_common_req_keys.WhatsappClientMessage + err = json.Unmarshal(msg, &whatsappClientMessage) + if err != nil { + log.Println("Error unmarshalling message:", err) + continue + } + if whatsappClientMessage.Type == soham_common_req_keys.STATUS_MESSAGE { + statusString, ok := whatsappClientMessage.Message.(string) + if !ok { + log.Printf("Invalid status message format: %+v\n", whatsappClientMessage) + continue + } + status, ok := soham_common_req_keys.StringToEnumConnectionStatus(statusString) + if ok { + // fmt.Printf("ConnectionStatus ======> %s\n", status) + if status == soham_common_req_keys.LOGGED_IN { + log.Printf("Loggedin =====> %d", numberToken) + } + soham_whatsapp_server_env.ConnectionNumberStatusMap[numberToken] = status + } + } else if whatsappClientMessage.ReqId != "" { + if ch, ok := soham_whatsapp_server_env.ReqestIdMap[whatsappClientMessage.ReqId]; ok { + ch <- whatsappClientMessage.Message + } else { + log.Printf("Request ID not found: %+v", whatsappClientMessage) + } + } else { + log.Printf("Unmarshalled Message: %+v", whatsappClientMessage) + } + } + delete(soham_whatsapp_server_env.WebsocketConnectionMap, numberToken) + delete(soham_whatsapp_server_env.ConnectionNumberStatusMap, numberToken) + log.Println("Websocket Connection Closed for Number Token:", numberToken) + log.Printf("Unsubscribed =====> %d", numberToken) +} diff --git a/servers/whatsapp-server/main.go b/servers/whatsapp-server/main.go index 98a4582..c585e34 100644 --- a/servers/whatsapp-server/main.go +++ b/servers/whatsapp-server/main.go @@ -10,9 +10,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/rpsoftech/golang-servers/env" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" "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" @@ -24,6 +24,7 @@ var version string func main() { env.LoadEnv("whatsapp-server.env") + whatsapp_config.InitaliseWhatsappEnvAndConfig() println(version) go func() { os.RemoveAll("./tmp") diff --git a/servers/whatsapp-server/src/apis/append.token.go b/servers/whatsapp-server/src/apis/append.token.go index f3bb93f..b2e96a4 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/functions/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..fde9a38 100644 --- a/servers/whatsapp-server/src/apis/start.number.go +++ b/servers/whatsapp-server/src/apis/start.number.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/gofiber/fiber/v2" - whatsapp_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" + whatsapp_config "github.com/rpsoftech/golang-servers/functions/whatsapp/config" whatsapp_functions "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/functions" "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/whatsapp" ) diff --git a/servers/whatsapp-server/src/config/index.go b/servers/whatsapp-server/src/config/index.go deleted file mode 100644 index 498b19c..0000000 --- a/servers/whatsapp-server/src/config/index.go +++ /dev/null @@ -1,98 +0,0 @@ -package whatsapp_config - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "os" - "path/filepath" - "strconv" - - "github.com/rpsoftech/golang-servers/env" - utility_functions "github.com/rpsoftech/golang-servers/utility/functions" - "github.com/rpsoftech/golang-servers/validator" -) - -var ( - Env *EnvInterface - WhatsappNumberConfigMap *IServerConfig - CurrentDirectory string = "" - serverConfigFilePath string = "" -) - -const ServerConfigFileName = "server.config.json" - -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"` - } -) - -func init() { - CurrentDirectory = env.FindAndReturnCurrentDir() - WhatsappNumberConfigMap = ReadConfigFileAndReturnIt(CurrentDirectory) - allow_local_no_Auth, err := strconv.ParseBool(os.Getenv(allow_local_no_auth_KEY)) - if err != nil { - log.Fatal(err) - } - auto_connect_to_whatsapp, err := strconv.ParseBool(os.Getenv(Auto_Connect_To_Whatsapp_KEY)) - if err != nil { - log.Fatal(err) - } - open_browser_for_scan_KEY, err := strconv.ParseBool(os.Getenv(open_browser_for_scan_KEY)) - if err != nil { - log.Fatal(err) - } - - Env = &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, - } - env.ValidateEnv(Env) -} - -func (sc *IServerConfig) Save() { - if serverConfigFilePath == "" { - serverConfigFilePath = filepath.Join(CurrentDirectory, ServerConfigFileName) - } - byteJson, err := json.MarshalIndent(sc, "", " ") - if err != nil { - return - } - f, err := os.OpenFile(serverConfigFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - fmt.Printf("%v \n", err) - return - } - defer f.Close() - if _, err = f.Write(byteJson); err != nil { - fmt.Printf("%v \n", err) - } -} - -func ReadConfigFileAndReturnIt(currentDir string) *IServerConfig { - config := new(IServerConfig) - configFilePAth := filepath.Join(currentDir, ServerConfigFileName) - if _, err := utility_functions.Exist(configFilePAth); errors.Is(err, os.ErrNotExist) { - panic(fmt.Errorf("CONFIG_NOT_EXIST_ON_PATH %s", configFilePAth)) - } - 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) - } - return config -} diff --git a/servers/whatsapp-server/src/functions/common-req.functions.go b/servers/whatsapp-server/src/functions/common-req.functions.go index 9b571fd..88f0297 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_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" ) func ExtractKeyFromHeader(c *fiber.Ctx, key string) string { @@ -17,7 +17,7 @@ func ExtractKeyFromHeader(c *fiber.Ctx, key string) string { } } func ExtractNumberFromCtx(c *fiber.Ctx) (string, error) { - id, ok := c.Locals(whatsapp_config.REQ_LOCAL_NUMBER_KEY).(string) + id, ok := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY).(string) if !ok { return "", &interfaces.RequestError{ StatusCode: http.StatusForbidden, diff --git a/servers/whatsapp-server/src/middleware/token-validator.go b/servers/whatsapp-server/src/middleware/token-validator.go index 11977b8..aa075f7 100644 --- a/servers/whatsapp-server/src/middleware/token-validator.go +++ b/servers/whatsapp-server/src/middleware/token-validator.go @@ -3,8 +3,9 @@ package whatsapp_server_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_config "github.com/rpsoftech/golang-servers/servers/whatsapp-server/src/config" + whatsapp_interfaces "github.com/rpsoftech/golang-servers/interfaces/whatsapp" ) // fiber middleware for jwt @@ -33,13 +34,13 @@ func TokenDecrypter(c *fiber.Ctx) error { Name: "ERROR_INVALID_TOKEN", }) } else { - c.Locals(whatsapp_config.REQ_LOCAL_NUMBER_KEY, tokenString[0]) + c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY, tokenString[0]) } return c.Next() } func AllowOnlyValidTokenMiddleWare(c *fiber.Ctx) error { - jwtRawFromLocal := c.Locals(whatsapp_config.REQ_LOCAL_NUMBER_KEY) + jwtRawFromLocal := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY) localError := c.Locals(interfaces.REQ_LOCAL_ERROR_KEY) if jwtRawFromLocal == nil { @@ -66,7 +67,7 @@ func AllowOnlyValidTokenMiddleWare(c *fiber.Ctx) error { } func AllowOnlyValidLoggedInWhatsapp(c *fiber.Ctx) error { - jwtRawFromLocal := c.Locals(whatsapp_config.REQ_LOCAL_NUMBER_KEY) + jwtRawFromLocal := c.Locals(whatsapp_interfaces.REQ_LOCAL_NUMBER_KEY) token, ok := jwtRawFromLocal.(string) if !ok { return &interfaces.RequestError{ 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..798ece4 100644 --- a/servers/whatsapp-server/src/whatsapp/interfaces.go +++ b/servers/whatsapp-server/src/whatsapp/interfaces.go @@ -11,10 +11,10 @@ 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" - 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_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" @@ -106,7 +106,6 @@ func (connection *WhatsappConnection) eventHandler(evt interface{}) { 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: @@ -114,7 +113,6 @@ func (connection *WhatsappConnection) eventHandler(evt interface{}) { 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() }() diff --git a/test.go b/test.go index 459d4fe..6d657be 100644 --- a/test.go +++ b/test.go @@ -2,14 +2,10 @@ package main import ( "fmt" - "strconv" + "runtime" ) func main() { - num64 := int64(12345) - str := strconv.FormatInt(num64, 10) // Decimal string - hexStr := strconv.FormatInt(num64, 16) // Hexadecimal string - - fmt.Println("Decimal string:", str) - fmt.Println("Hexadecimal string:", hexStr) + fmt.Println(runtime.GOOS) + fmt.Println(runtime.GOARCH) } diff --git a/utility/functions/download-file.go b/utility/functions/download-file.go new file mode 100644 index 0000000..a8fb17c --- /dev/null +++ b/utility/functions/download-file.go @@ -0,0 +1,24 @@ +package utility_functions + +import ( + "io" + "net/http" + "os" +) + +func DownloadFile(filepath string, url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return err +} diff --git a/utility/functions/stream.logs.go b/utility/functions/stream.logs.go new file mode 100644 index 0000000..49e6b52 --- /dev/null +++ b/utility/functions/stream.logs.go @@ -0,0 +1,19 @@ +package utility_functions + +import ( + "bufio" + "io" + "log" +) + +func StreamLogs(pipe io.ReadCloser, prefix string) { + + scanner := bufio.NewScanner(pipe) + + for scanner.Scan() { + + line := scanner.Text() + + log.Printf("[%s] %s\n", prefix, line) + } +} diff --git a/utility/functions/uuid.v5.go b/utility/functions/uuid.v5.go new file mode 100644 index 0000000..a96252b --- /dev/null +++ b/utility/functions/uuid.v5.go @@ -0,0 +1,7 @@ +package utility_functions + +import "github.com/google/uuid" + +func UUIDv5(nameSpace uuid.UUID, data string) string { + return uuid.NewSHA1(nameSpace, []byte(data)).String() +} diff --git a/utility/functions/validate-url.go b/utility/functions/validate-url.go index f76c1bc..3cb4fea 100644 --- a/utility/functions/validate-url.go +++ b/utility/functions/validate-url.go @@ -1,5 +1,8 @@ package utility_functions -func ValidateUrl(url string) bool { - return true +import "net/url" + +func ValidateUrl(urlString string) bool { + _, err := url.ParseRequestURI(urlString) + return err == nil } 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 diff --git a/windwos.sh b/windwos.sh index ddd2d62..bbcc56c 100644 --- a/windwos.sh +++ b/windwos.sh @@ -11,4 +11,6 @@ GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/mysql-to-surreal.exe" GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/main-server.exe" -v -ldflags="-s -w" ./servers/jwelly/main-server/main.go GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/mysql-backup.exe" -v -ldflags="-s -w" ./servers/jwelly/mysql-backup/main.go GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/mysql-to-mysql.exe" -v -ldflags="-s -w" ./servers/jwelly/mysql-to-mysql/main.go -GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/mysql-backup-cmd.exe" -v -ldflags="-s -w" ./servers/jwelly/mysql-backup-cmd/main.go \ No newline at end of file +GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/mysql-backup-cmd.exe" -v -ldflags="-s -w" ./servers/jwelly/mysql-backup-cmd/main.go +GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/soham/whatsapp-server.exe" -v -ldflags="-s -w" ./servers/soham/whatsapp-server/main.go +GOOS=windows GOARCH=amd64 go build -o "dist/windows-amd64/soham/whatsapp-client.exe" -v -ldflags="-s -w" ./servers/soham/whatsapp-client/main.go