From 20df78506ab82cf4718e1b584a621436588af7d4 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sun, 23 Feb 2025 21:58:44 +0700 Subject: [PATCH 1/4] refactor: Update Dockerfile build process and improve metadata handling in utils --- docker-compose.yml | 2 +- docker/golang.Dockerfile | 6 +++--- src/go.mod | 24 ++++++++++++------------ src/go.sum | 21 +++++++++++++++++++++ src/pkg/utils/general.go | 26 ++++++++++++++++++++++---- src/pkg/utils/general_test.go | 3 ++- src/pkg/whatsapp/webhook.go | 2 +- src/services/send.go | 17 +++++++++++------ 8 files changed, 73 insertions(+), 28 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 888a1d0..c49d887 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: build: context: . dockerfile: ./docker/golang.Dockerfile - restart: 'always' + restart: "on-failure" ports: - "3000:3000" env_file: diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index 155605e..cc919aa 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -8,14 +8,14 @@ COPY ./src . # Fetch dependencies. RUN go mod download -# Build the binary. -RUN go build -o /app/whatsapp +# Build the binary with optimizations +RUN go build -a -ldflags="-w -s" -o /app/whatsapp ############################# ## STEP 2 build a smaller image ############################# FROM alpine:3.20 -RUN apk update && apk add --no-cache ffmpeg +RUN apk add --no-cache ffmpeg WORKDIR /app # Copy compiled from builder. COPY --from=builder /app/whatsapp /app/whatsapp diff --git a/src/go.mod b/src/go.mod index d930503..d7cd049 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,11 +1,11 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice -go 1.23 +go 1.23.0 -toolchain go1.23.1 +toolchain go1.24.0 require ( - github.com/PuerkitoBio/goquery v1.10.1 + github.com/PuerkitoBio/goquery v1.10.2 github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.1 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 @@ -17,12 +17,12 @@ require ( github.com/mattn/go-sqlite3 v1.14.24 github.com/sirupsen/logrus v1.9.3 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - github.com/valyala/fasthttp v1.58.0 - go.mau.fi/libsignal v0.1.1 - go.mau.fi/whatsmeow v0.0.0-20250210124836-838d5eeaf73e + github.com/valyala/fasthttp v1.59.0 + go.mau.fi/libsignal v0.1.2 + go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1 google.golang.org/protobuf v1.36.5 ) @@ -39,7 +39,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -59,12 +59,12 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - go.mau.fi/util v0.8.4 // indirect + go.mau.fi/util v0.8.5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect + golang.org/x/crypto v0.34.0 // indirect + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/image v0.24.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/src/go.sum b/src/go.sum index d92a42b..b3721dd 100644 --- a/src/go.sum +++ b/src/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= +github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= +github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= @@ -11,6 +13,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -50,6 +53,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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= @@ -104,6 +109,8 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -120,6 +127,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= +github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= +github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -127,10 +136,16 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI= go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw= +go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= +go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ= go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY= +go.mau.fi/util v0.8.5 h1:PwCAAtcfK0XxZ4sdErJyfBMkTEWoQU33aB7QqDDzQRI= +go.mau.fi/util v0.8.5/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= go.mau.fi/whatsmeow v0.0.0-20250210124836-838d5eeaf73e h1:29JB5UMhd3kzLj0GpKUnZM8ZpyP3o9Kd0iDRtXgLvog= go.mau.fi/whatsmeow v0.0.0-20250210124836-838d5eeaf73e/go.mod h1:PG1x7fBW66I9q/e8a9mU2qF9M94+kK32MceMWgxBoiw= +go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1 h1:mqlGS29j1rtg4Wl7VbRyd6yHBqLLgvUN2EMnNQ6ZiSY= +go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -141,8 +156,12 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= +golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= @@ -162,6 +181,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/src/pkg/utils/general.go b/src/pkg/utils/general.go index 90c8b80..99faff9 100644 --- a/src/pkg/utils/general.go +++ b/src/pkg/utils/general.go @@ -69,20 +69,22 @@ type Metadata struct { Description string Image string ImageThumb []byte + Height *uint32 + Width *uint32 } -func GetMetaDataFromURL(url string) (meta Metadata) { +func GetMetaDataFromURL(url string) (meta Metadata, err error) { // Send an HTTP GET request to the website response, err := http.Get(url) if err != nil { - log.Fatal(err) + return meta, err } defer response.Body.Close() // Parse the HTML document document, err := goquery.NewDocumentFromReader(response.Body) if err != nil { - log.Fatal(err) + return meta, err } document.Find("meta[name='description']").Each(func(index int, element *goquery.Selection) { @@ -98,6 +100,22 @@ func GetMetaDataFromURL(url string) (meta Metadata) { meta.Image, _ = element.Attr("content") }) + document.Find("meta[property='og:image:width']").Each(func(index int, element *goquery.Selection) { + if content, exists := element.Attr("content"); exists { + width, _ := strconv.Atoi(content) + widthUint32 := uint32(width) + meta.Width = &widthUint32 + } + }) + + document.Find("meta[property='og:image:height']").Each(func(index int, element *goquery.Selection) { + if content, exists := element.Attr("content"); exists { + height, _ := strconv.Atoi(content) + heightUint32 := uint32(height) + meta.Height = &heightUint32 + } + }) + // If an og:image is found, download it and store its content in ImageThumb if meta.Image != "" { imageResponse, err := http.Get(meta.Image) @@ -114,7 +132,7 @@ func GetMetaDataFromURL(url string) (meta Metadata) { } } - return meta + return meta, nil } // ContainsMention is checking if message contains mention, then return only mention without @ diff --git a/src/pkg/utils/general_test.go b/src/pkg/utils/general_test.go index b16c2bf..8883454 100644 --- a/src/pkg/utils/general_test.go +++ b/src/pkg/utils/general_test.go @@ -93,7 +93,8 @@ func (suite *UtilsTestSuite) TestGetMetaDataFromURL() { })) defer server.Close() // Ensure the server is closed when the test ends - meta := utils.GetMetaDataFromURL(server.URL) + meta, err := utils.GetMetaDataFromURL(server.URL) + assert.NoError(suite.T(), err) assert.Equal(suite.T(), "Test Title", meta.Title) assert.Equal(suite.T(), "Test Description", meta.Description) assert.Equal(suite.T(), "http://example.com/image.jpg", meta.Image) diff --git a/src/pkg/whatsapp/webhook.go b/src/pkg/whatsapp/webhook.go index 307b79d..5a1dc24 100644 --- a/src/pkg/whatsapp/webhook.go +++ b/src/pkg/whatsapp/webhook.go @@ -41,7 +41,7 @@ func createPayload(evt *events.Message) (map[string]interface{}, error) { if from := evt.Info.SourceString(); from != "" { body["from"] = from } - if message.Text != "" { + if message.ID != "" { body["message"] = message } if pushname := evt.Info.PushName; pushname != "" { diff --git a/src/services/send.go b/src/services/send.go index 0e82961..9a9afa2 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -430,14 +430,19 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link return response, err } - getMetaDataFromURL := utils.GetMetaDataFromURL(request.Link) + getMetaDataFromURL, err := utils.GetMetaDataFromURL(request.Link) + if err != nil { + return response, err + } msg := &waE2E.Message{ExtendedTextMessage: &waE2E.ExtendedTextMessage{ - Text: proto.String(fmt.Sprintf("%s\n%s", request.Caption, request.Link)), - Title: proto.String(getMetaDataFromURL.Title), - MatchedText: proto.String(request.Link), - Description: proto.String(getMetaDataFromURL.Description), - JPEGThumbnail: getMetaDataFromURL.ImageThumb, + Text: proto.String(fmt.Sprintf("%s\n%s", request.Caption, request.Link)), + Title: proto.String(getMetaDataFromURL.Title), + MatchedText: proto.String(request.Link), + Description: proto.String(getMetaDataFromURL.Description), + JPEGThumbnail: getMetaDataFromURL.ImageThumb, + ThumbnailHeight: getMetaDataFromURL.Height, + ThumbnailWidth: getMetaDataFromURL.Width, }} content := "🔗 " + request.Link From e83c9d2893c027bdb39ca8611a7e98543cf8d9b0 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Wed, 26 Feb 2025 07:48:26 +0700 Subject: [PATCH 2/4] feat: Update app version to v5.3.0 and upgrade Go version to 1.24 feat: add MyListContacts endpoint --- .github/workflows/release-linux.yml | 4 +- .github/workflows/release-mac.yml | 2 +- .github/workflows/release-windows.yml | 2 +- docker/golang.Dockerfile | 2 +- docs/openapi.yaml | 47 +++++++++++- readme.md | 1 + src/config/settings.go | 2 +- src/domains/user/account.go | 12 +++- src/domains/user/user.go | 1 + src/go.mod | 9 +-- src/go.sum | 32 ++------- src/internal/rest/message.go | 5 +- src/internal/rest/user.go | 13 ++++ src/services/user.go | 18 +++++ src/views/components/AccountAvatar.js | 2 +- src/views/components/AccountChangeAvatar.js | 2 +- src/views/components/AccountContact.js | 79 +++++++++++++++++++++ src/views/components/AccountPrivacy.js | 2 +- src/views/components/AccountUserInfo.js | 2 +- src/views/index.html | 4 +- 20 files changed, 191 insertions(+), 50 deletions(-) create mode 100644 src/views/components/AccountContact.js diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 8ecb881..16bc794 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -18,7 +18,7 @@ jobs: - name: Golang Installation uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' - name: Golang build run: | cd src && go build -o linux-amd64 @@ -38,7 +38,7 @@ jobs: - name: Golang Installation uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' - name: Golang build run: | cd src && go build -o linux-arm64 diff --git a/.github/workflows/release-mac.yml b/.github/workflows/release-mac.yml index 6f47198..bf41482 100644 --- a/.github/workflows/release-mac.yml +++ b/.github/workflows/release-mac.yml @@ -18,7 +18,7 @@ jobs: - name: Golang Installation uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' - name: Golang build run: | cd src && go build -o darwin-amd64 diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 820183e..104fb9a 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -18,7 +18,7 @@ jobs: - name: Golang Installation uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' - name: Golang build run: | cd src && go build -o windows-amd64.exe diff --git a/docker/golang.Dockerfile b/docker/golang.Dockerfile index cc919aa..a8bd640 100644 --- a/docker/golang.Dockerfile +++ b/docker/golang.Dockerfile @@ -1,7 +1,7 @@ ############################ # STEP 1 build executable binary ############################ -FROM golang:1.23-alpine3.20 AS builder +FROM golang:1.24-alpine3.20 AS builder RUN apk update && apk add --no-cache gcc musl-dev gcompat WORKDIR /whatsapp COPY ./src . diff --git a/docs/openapi.yaml b/docs/openapi.yaml index cdcd0ed..2a9d583 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" info: title: WhatsApp API MultiDevice - version: 5.1.0 + version: 5.2.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -282,6 +282,26 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /user/my/contacts: + get: + operationId: userMyContacts + tags: + - user + summary: Get list of user contacts + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MyListContactsResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' + /send/message: post: operationId: sendMessage @@ -1646,6 +1666,31 @@ components: role: type: string example: "subscriber" + MyListContactsResponse: + type: object + properties: + code: + type: string + example: "SUCCESS" + message: + type: string + example: "Success get list contacts" + results: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/MyListContacts' + MyListContacts: + type: object + properties: + jid: + type: string + example: "628123123123123@s.whatsapp.net" + name: + type: string + example: "Aldino Kemal" GroupResponse: type: object properties: diff --git a/readme.md b/readme.md index 5955881..67267bf 100644 --- a/readme.md +++ b/readme.md @@ -187,6 +187,7 @@ You can fork or edit this source code ! | ✅ | User My Groups | GET | /user/my/groups | | ✅ | User My Newsletter | GET | /user/my/newsletters | | ✅ | User My Privacy Setting | GET | /user/my/privacy | +| ✅ | User My Contacts | GET | /user/my/contacts | | ✅ | Send Message | POST | /send/message | | ✅ | Send Image | POST | /send/image | | ✅ | Send Audio | POST | /send/audio | diff --git a/src/config/settings.go b/src/config/settings.go index 492514c..369cdc3 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v5.2.0" + AppVersion = "v5.3.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/domains/user/account.go b/src/domains/user/account.go index 039c373..fba3b0b 100644 --- a/src/domains/user/account.go +++ b/src/domains/user/account.go @@ -1,8 +1,9 @@ package user import ( - "go.mau.fi/whatsmeow/types" "mime/multipart" + + "go.mau.fi/whatsmeow/types" ) type InfoRequest struct { @@ -59,3 +60,12 @@ type MyListNewsletterResponse struct { type ChangeAvatarRequest struct { Avatar *multipart.FileHeader `json:"avatar" form:"avatar"` } + +type MyListContactsResponse struct { + Data []MyListContactsResponseData `json:"data"` +} + +type MyListContactsResponseData struct { + JID types.JID `json:"jid"` + Name string `json:"name"` +} diff --git a/src/domains/user/user.go b/src/domains/user/user.go index 7d47959..0b46309 100644 --- a/src/domains/user/user.go +++ b/src/domains/user/user.go @@ -11,4 +11,5 @@ type IUserService interface { MyListGroups(ctx context.Context) (response MyListGroupsResponse, err error) MyListNewsletter(ctx context.Context) (response MyListNewsletterResponse, err error) MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error) + MyListContacts(ctx context.Context) (response MyListContactsResponse, err error) } diff --git a/src/go.mod b/src/go.mod index d7cd049..b657b16 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,8 +1,6 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice -go 1.23.0 - -toolchain go1.24.0 +go 1.24.0 require ( github.com/PuerkitoBio/goquery v1.10.2 @@ -22,7 +20,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.59.0 go.mau.fi/libsignal v0.1.2 - go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1 + go.mau.fi/whatsmeow v0.0.0-20250225112721-b7530f3a5056 google.golang.org/protobuf v1.36.5 ) @@ -58,10 +56,9 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect go.mau.fi/util v0.8.5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.34.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/image v0.24.0 // indirect golang.org/x/net v0.35.0 // indirect diff --git a/src/go.sum b/src/go.sum index b3721dd..9c88314 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,7 +1,5 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= -github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= @@ -12,7 +10,6 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -51,8 +48,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -107,11 +102,8 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -125,27 +117,17 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= -github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI= -go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= -go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ= -go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY= go.mau.fi/util v0.8.5 h1:PwCAAtcfK0XxZ4sdErJyfBMkTEWoQU33aB7QqDDzQRI= go.mau.fi/util v0.8.5/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= -go.mau.fi/whatsmeow v0.0.0-20250210124836-838d5eeaf73e h1:29JB5UMhd3kzLj0GpKUnZM8ZpyP3o9Kd0iDRtXgLvog= -go.mau.fi/whatsmeow v0.0.0-20250210124836-838d5eeaf73e/go.mod h1:PG1x7fBW66I9q/e8a9mU2qF9M94+kK32MceMWgxBoiw= -go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1 h1:mqlGS29j1rtg4Wl7VbRyd6yHBqLLgvUN2EMnNQ6ZiSY= -go.mau.fi/whatsmeow v0.0.0-20250221160813-35b965ceadf1/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= +go.mau.fi/whatsmeow v0.0.0-20250225112721-b7530f3a5056 h1:1JQUOpYXhFSEQgXMEWD/ZH38FrIe5i1yjxSBwa0aN/Q= +go.mau.fi/whatsmeow v0.0.0-20250225112721-b7530f3a5056/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -154,12 +136,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= -golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -179,8 +157,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/src/internal/rest/message.go b/src/internal/rest/message.go index 8ec7ad1..8d6d9fc 100644 --- a/src/internal/rest/message.go +++ b/src/internal/rest/message.go @@ -1,7 +1,6 @@ package rest import ( - "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" @@ -82,7 +81,7 @@ func (controller *Message) UpdateMessage(c *fiber.Ctx) error { } func (controller *Message) ReactMessage(c *fiber.Ctx) error { - var request message.ReactionRequest + var request domainMessage.ReactionRequest err := c.BodyParser(&request) utils.PanicIfNeeded(err) @@ -101,7 +100,7 @@ func (controller *Message) ReactMessage(c *fiber.Ctx) error { } func (controller *Message) MarkAsRead(c *fiber.Ctx) error { - var request message.MarkAsReadRequest + var request domainMessage.MarkAsReadRequest err := c.BodyParser(&request) utils.PanicIfNeeded(err) diff --git a/src/internal/rest/user.go b/src/internal/rest/user.go index c357d6f..10596a5 100644 --- a/src/internal/rest/user.go +++ b/src/internal/rest/user.go @@ -19,6 +19,7 @@ func InitRestUser(app *fiber.App, service domainUser.IUserService) User { app.Get("/user/my/privacy", rest.UserMyPrivacySetting) app.Get("/user/my/groups", rest.UserMyListGroups) app.Get("/user/my/newsletters", rest.UserMyListNewsletter) + app.Get("/user/my/contacts", rest.UserMyListContacts) return rest } @@ -112,3 +113,15 @@ func (controller *User) UserMyListNewsletter(c *fiber.Ctx) error { Results: response, }) } + +func (controller *User) UserMyListContacts(c *fiber.Ctx) error { + response, err := controller.Service.MyListContacts(c.UserContext()) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success get list contacts", + Results: response, + }) +} diff --git a/src/services/user.go b/src/services/user.go index b6636bb..e1d0c94 100644 --- a/src/services/user.go +++ b/src/services/user.go @@ -160,6 +160,24 @@ func (service userService) MyPrivacySetting(_ context.Context) (response domainU return response, nil } +func (service userService) MyListContacts(ctx context.Context) (response domainUser.MyListContactsResponse, err error) { + whatsapp.MustLogin(service.WaCli) + + contacts, err := service.WaCli.Store.Contacts.GetAllContacts() + if err != nil { + return + } + + for jid, contact := range contacts { + response.Data = append(response.Data, domainUser.MyListContactsResponseData{ + JID: jid, + Name: contact.FullName, + }) + } + + return response, nil +} + func (service userService) ChangeAvatar(ctx context.Context, request domainUser.ChangeAvatarRequest) (err error) { whatsapp.MustLogin(service.WaCli) diff --git a/src/views/components/AccountAvatar.js b/src/views/components/AccountAvatar.js index 9d7b292..9a52403 100644 --- a/src/views/components/AccountAvatar.js +++ b/src/views/components/AccountAvatar.js @@ -65,7 +65,7 @@ export default { } }, template: ` -
+
Account
Avatar
diff --git a/src/views/components/AccountChangeAvatar.js b/src/views/components/AccountChangeAvatar.js index 6c8596b..7c109cc 100644 --- a/src/views/components/AccountChangeAvatar.js +++ b/src/views/components/AccountChangeAvatar.js @@ -63,7 +63,7 @@ export default { } }, template: ` -
+
Account
Change Avatar
diff --git a/src/views/components/AccountContact.js b/src/views/components/AccountContact.js new file mode 100644 index 0000000..a907e05 --- /dev/null +++ b/src/views/components/AccountContact.js @@ -0,0 +1,79 @@ +export default { + name: 'AccountContact', + data() { + return { + contacts: [] + } + }, + methods: { + async openModal() { + try { + this.dtClear() + await this.submitApi(); + $('#modalContactList').modal('show'); + this.dtRebuild() + showSuccessInfo("Contacts fetched") + } catch (err) { + showErrorInfo(err) + } + }, + dtClear() { + $('#account_contacts_table').DataTable().destroy(); + }, + dtRebuild() { + $('#account_contacts_table').DataTable({ + "pageLength": 10, + "reloadData": true, + }).draw(); + }, + async submitApi() { + try { + let response = await window.http.get(`/user/my/contacts`) + this.contacts = response.data.results.data; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } + }, + getPhoneNumber(jid) { + return jid.split('@')[0]; + } + }, + template: ` +
+
+ Contacts +
My Contacts
+
+ Display all your contacts +
+
+
+ + + + ` +} diff --git a/src/views/components/AccountPrivacy.js b/src/views/components/AccountPrivacy.js index 58d35ab..fb47368 100644 --- a/src/views/components/AccountPrivacy.js +++ b/src/views/components/AccountPrivacy.js @@ -28,7 +28,7 @@ export default { }, }, template: ` -
+
Account
My Privacy Setting
diff --git a/src/views/components/AccountUserInfo.js b/src/views/components/AccountUserInfo.js index 56b5494..baa7a3e 100644 --- a/src/views/components/AccountUserInfo.js +++ b/src/views/components/AccountUserInfo.js @@ -72,7 +72,7 @@ export default { } }, template: ` -
+
Account
User Info
diff --git a/src/views/index.html b/src/views/index.html index 67590cf..ff1c486 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -157,6 +157,7 @@ +
@@ -218,6 +219,7 @@ import AccountUserInfo from "./components/AccountUserInfo.js"; import AccountPrivacy from "./components/AccountPrivacy.js"; import NewsletterList from "./components/NewsletterList.js"; + import AccountContact from "./components/AccountContact.js"; const showErrorInfo = (message) => { $('body').toast({ @@ -256,7 +258,7 @@ MessageDelete, MessageUpdate, MessageReact, MessageRevoke, GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants, NewsletterList, - AccountAvatar, AccountUserInfo, AccountPrivacy, AccountChangeAvatar + AccountAvatar, AccountUserInfo, AccountPrivacy, AccountChangeAvatar, AccountContact }, delimiters: ['[[', ']]'], data() { From 8fc9fc7bc0ce27278defb4cef8a3dec208b86207 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sat, 1 Mar 2025 07:02:36 +0700 Subject: [PATCH 3/4] refactor: Improve WhatsApp event handling and code organization - Refactor `init.go` to break down complex event handler into smaller, focused functions - Add type definitions and comments to improve code readability - Extract event handling logic into separate functions for better maintainability - Improve error handling and logging in database initialization - Add `FormatJID` utility function to handle JID formatting - Update README with minor formatting improvements --- readme.md | 4 +- src/pkg/whatsapp/init.go | 323 ++++++++++++++++++++++++-------------- src/pkg/whatsapp/utils.go | 12 ++ 3 files changed, 223 insertions(+), 116 deletions(-) diff --git a/readme.md b/readme.md index 67267bf..87f1551 100644 --- a/readme.md +++ b/readme.md @@ -38,7 +38,8 @@ Now that we support ARM64 for Linux: - `--webhook="http://yourwebhook.site/handler"`, or you can simplify - `-w="http://yourwebhook.site/handler"` - Webhook Secret - Our webhook will be sent to you with an HMAC header and a sha256 default key `secret`.
+ Our webhook will be sent to you with an HMAC header and a sha256 default key `secret`. + You may modify this by using the option below: - `--webhook-secret="secret"` @@ -53,6 +54,7 @@ You can configure the application using either command-line flags (shown above) ### Environment Variables To use environment variables: + 1. Copy `.env.example` to `.env` in your project root 2. Modify the values in `.env` according to your needs 3. Or set the same variables as system environment variables diff --git a/src/pkg/whatsapp/init.go b/src/pkg/whatsapp/init.go index c81835a..f5ecc4f 100644 --- a/src/pkg/whatsapp/init.go +++ b/src/pkg/whatsapp/init.go @@ -25,13 +25,7 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - cli *whatsmeow.Client - log waLog.Logger - historySyncID int32 - startupTime = time.Now().Unix() -) - +// Type definitions type ExtractedMedia struct { MediaPath string `json:"media_path"` MimeType string `json:"mime_type"` @@ -50,29 +44,40 @@ type evtMessage struct { QuotedMessage string `json:"quoted_message,omitempty"` } +// Global variables +var ( + cli *whatsmeow.Client + log waLog.Logger + historySyncID int32 + startupTime = time.Now().Unix() +) + +// InitWaDB initializes the WhatsApp database connection func InitWaDB() *sqlstore.Container { - // Running Whatsapp log = waLog.Stdout("Main", config.WhatsappLogLevel, true) dbLog := waLog.Stdout("Database", config.WhatsappLogLevel, true) - var storeContainer *sqlstore.Container - var err error + storeContainer, err := initDatabase(dbLog) + if err != nil { + log.Errorf("Database initialization error: %v", err) + panic(pkgError.InternalServerError(fmt.Sprintf("Database initialization error: %v", err))) + } + + return storeContainer +} + +// initDatabase creates and returns a database store container based on the configured URI +func initDatabase(dbLog waLog.Logger) (*sqlstore.Container, error) { if strings.HasPrefix(config.DBURI, "file:") { - storeContainer, err = sqlstore.New("sqlite3", config.DBURI, dbLog) + return sqlstore.New("sqlite3", config.DBURI, dbLog) } else if strings.HasPrefix(config.DBURI, "postgres:") { - storeContainer, err = sqlstore.New("postgres", config.DBURI, dbLog) - } else { - log.Errorf("Unknown database type: %s", config.DBURI) - panic(pkgError.InternalServerError(fmt.Sprintf("Unknown database type: %s. Currently only sqlite3(file:) and postgres are supported", config.DBURI))) + return sqlstore.New("postgres", config.DBURI, dbLog) } - if err != nil { - log.Errorf("Failed to connect to database: %v", err) - panic(pkgError.InternalServerError(fmt.Sprintf("Failed to connect to database: %v", err))) - } - return storeContainer + return nil, fmt.Errorf("unknown database type: %s. Currently only sqlite3(file:) and postgres are supported", config.DBURI) } +// InitWaCLI initializes the WhatsApp client func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { device, err := storeContainer.GetFirstDevice() if err != nil { @@ -85,9 +90,12 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { panic("No device found") } + // Configure device properties osName := fmt.Sprintf("%s %s", config.AppOs, config.AppVersion) store.DeviceProps.PlatformType = &config.AppPlatform store.DeviceProps.Os = &osName + + // Create and configure the client cli = whatsmeow.NewClient(device, waLog.Stdout("Client", config.WhatsappLogLevel, true)) cli.EnableAutoReconnect = true cli.AutoTrustIdentity = true @@ -96,120 +104,205 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { return cli } +// handler is the main event handler for WhatsApp events func handler(rawEvt interface{}) { switch evt := rawEvt.(type) { case *events.DeleteForMe: - log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String()) + handleDeleteForMe(evt) case *events.AppStateSyncComplete: - if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { - err := cli.SendPresence(types.PresenceAvailable) - if err != nil { - log.Warnf("Failed to send available presence: %v", err) - } else { - log.Infof("Marked self as available") - } - } + handleAppStateSyncComplete(evt) case *events.PairSuccess: - websocket.Broadcast <- websocket.BroadcastMessage{ - Code: "LOGIN_SUCCESS", - Message: fmt.Sprintf("Successfully pair with %s", evt.ID.String()), - } + handlePairSuccess(evt) case *events.LoggedOut: - websocket.Broadcast <- websocket.BroadcastMessage{ - Code: "LIST_DEVICES", - Result: nil, - } + handleLoggedOut() case *events.Connected, *events.PushNameSetting: - if len(cli.Store.PushName) == 0 { - return - } + handleConnectionEvents() + case *events.StreamReplaced: + handleStreamReplaced() + case *events.Message: + handleMessage(evt) + case *events.Receipt: + handleReceipt(evt) + case *events.Presence: + handlePresence(evt) + case *events.HistorySync: + handleHistorySync(evt) + case *events.AppState: + handleAppState(evt) + } +} + +// Event handler functions + +func handleDeleteForMe(evt *events.DeleteForMe) { + log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String()) +} - // Send presence available when connecting and when the pushname is changed. - // This makes sure that outgoing messages always have the right pushname. - err := cli.SendPresence(types.PresenceAvailable) - if err != nil { +func handleAppStateSyncComplete(evt *events.AppStateSyncComplete) { + if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { + if err := cli.SendPresence(types.PresenceAvailable); err != nil { log.Warnf("Failed to send available presence: %v", err) } else { log.Infof("Marked self as available") } - case *events.StreamReplaced: - os.Exit(0) - case *events.Message: - metaParts := []string{ - fmt.Sprintf("pushname: %s", evt.Info.PushName), - fmt.Sprintf("timestamp: %s", evt.Info.Timestamp), - } - if evt.Info.Type != "" { - metaParts = append(metaParts, fmt.Sprintf("type: %s", evt.Info.Type)) - } - if evt.Info.Category != "" { - metaParts = append(metaParts, fmt.Sprintf("category: %s", evt.Info.Category)) - } - if evt.IsViewOnce { - metaParts = append(metaParts, "view once") - } + } +} - log.Infof("Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message) - message := ExtractMessageText(evt) - utils.RecordMessage(evt.Info.ID, evt.Info.Sender.String(), message) +func handlePairSuccess(evt *events.PairSuccess) { + websocket.Broadcast <- websocket.BroadcastMessage{ + Code: "LOGIN_SUCCESS", + Message: fmt.Sprintf("Successfully pair with %s", evt.ID.String()), + } +} - if img := evt.Message.GetImageMessage(); img != nil { - if path, err := ExtractMedia(config.PathStorages, img); err != nil { - log.Errorf("Failed to download image: %v", err) - } else { - log.Infof("Image downloaded to %s", path) - } - } +func handleLoggedOut() { + websocket.Broadcast <- websocket.BroadcastMessage{ + Code: "LIST_DEVICES", + Result: nil, + } +} - if config.WhatsappAutoReplyMessage != "" && - !isGroupJid(evt.Info.Chat.String()) && - !strings.Contains(evt.Info.SourceString(), "broadcast") { - _, _ = cli.SendMessage(context.Background(), evt.Info.Sender, &waE2E.Message{Conversation: proto.String(config.WhatsappAutoReplyMessage)}) - } +func handleConnectionEvents() { + if len(cli.Store.PushName) == 0 { + return + } - if len(config.WhatsappWebhook) > 0 && - !strings.Contains(evt.Info.SourceString(), "broadcast") && - !isFromMySelf(evt.Info.SourceString()) { - go func(evt *events.Message) { - if err := forwardToWebhook(evt); err != nil { - logrus.Error("Failed forward to webhook: ", err) - } - }(evt) - } - case *events.Receipt: - if evt.Type == types.ReceiptTypeRead || evt.Type == types.ReceiptTypeReadSelf { - log.Infof("%v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp) - } else if evt.Type == types.ReceiptTypeDelivered { - log.Infof("%s was delivered to %s at %s", evt.MessageIDs[0], evt.SourceString(), evt.Timestamp) + // Send presence available when connecting and when the pushname is changed. + // This makes sure that outgoing messages always have the right pushname. + if err := cli.SendPresence(types.PresenceAvailable); err != nil { + log.Warnf("Failed to send available presence: %v", err) + } else { + log.Infof("Marked self as available") + } +} + +func handleStreamReplaced() { + os.Exit(0) +} + +func handleMessage(evt *events.Message) { + // Log message metadata + metaParts := buildMessageMetaParts(evt) + log.Infof("Received message %s from %s (%s): %+v", + evt.Info.ID, + evt.Info.SourceString(), + strings.Join(metaParts, ", "), + evt.Message, + ) + + // Record the message + message := ExtractMessageText(evt) + utils.RecordMessage(evt.Info.ID, evt.Info.Sender.String(), message) + + // Handle image message if present + handleImageMessage(evt) + + // Handle auto-reply if configured + handleAutoReply(evt) + + // Forward to webhook if configured + handleWebhookForward(evt) +} + +func buildMessageMetaParts(evt *events.Message) []string { + metaParts := []string{ + fmt.Sprintf("pushname: %s", evt.Info.PushName), + fmt.Sprintf("timestamp: %s", evt.Info.Timestamp), + } + if evt.Info.Type != "" { + metaParts = append(metaParts, fmt.Sprintf("type: %s", evt.Info.Type)) + } + if evt.Info.Category != "" { + metaParts = append(metaParts, fmt.Sprintf("category: %s", evt.Info.Category)) + } + if evt.IsViewOnce { + metaParts = append(metaParts, "view once") + } + return metaParts +} + +func handleImageMessage(evt *events.Message) { + if img := evt.Message.GetImageMessage(); img != nil { + if path, err := ExtractMedia(config.PathStorages, img); err != nil { + log.Errorf("Failed to download image: %v", err) + } else { + log.Infof("Image downloaded to %s", path) } - case *events.Presence: - if evt.Unavailable { - if evt.LastSeen.IsZero() { - log.Infof("%s is now offline", evt.From) - } else { - log.Infof("%s is now offline (last seen: %s)", evt.From, evt.LastSeen) + } +} + +func handleAutoReply(evt *events.Message) { + if config.WhatsappAutoReplyMessage != "" && + !isGroupJid(evt.Info.Chat.String()) && + !evt.Info.IsIncomingBroadcast() && + evt.Message.GetExtendedTextMessage().GetText() != "" { + _, _ = cli.SendMessage( + context.Background(), + FormatJID(evt.Info.Sender.String()), + &waE2E.Message{Conversation: proto.String(config.WhatsappAutoReplyMessage)}, + ) + } +} + +func handleWebhookForward(evt *events.Message) { + if len(config.WhatsappWebhook) > 0 && + !strings.Contains(evt.Info.SourceString(), "broadcast") && + !isFromMySelf(evt.Info.SourceString()) { + go func(evt *events.Message) { + if err := forwardToWebhook(evt); err != nil { + logrus.Error("Failed forward to webhook: ", err) } + }(evt) + } +} + +func handleReceipt(evt *events.Receipt) { + if evt.Type == types.ReceiptTypeRead || evt.Type == types.ReceiptTypeReadSelf { + log.Infof("%v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp) + } else if evt.Type == types.ReceiptTypeDelivered { + log.Infof("%s was delivered to %s at %s", evt.MessageIDs[0], evt.SourceString(), evt.Timestamp) + } +} + +func handlePresence(evt *events.Presence) { + if evt.Unavailable { + if evt.LastSeen.IsZero() { + log.Infof("%s is now offline", evt.From) } else { - log.Infof("%s is now online", evt.From) - } - case *events.HistorySync: - id := atomic.AddInt32(&historySyncID, 1) - fileName := fmt.Sprintf("%s/history-%d-%s-%d-%s.json", config.PathStorages, startupTime, cli.Store.ID.String(), id, evt.Data.SyncType.String()) - file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - log.Errorf("Failed to open file to write history sync: %v", err) - return + log.Infof("%s is now offline (last seen: %s)", evt.From, evt.LastSeen) } - enc := json.NewEncoder(file) - enc.SetIndent("", " ") - err = enc.Encode(evt.Data) - if err != nil { - log.Errorf("Failed to write history sync: %v", err) - return - } - log.Infof("Wrote history sync to %s", fileName) - _ = file.Close() - case *events.AppState: - log.Debugf("App state event: %+v / %+v", evt.Index, evt.SyncActionValue) + } else { + log.Infof("%s is now online", evt.From) } } + +func handleHistorySync(evt *events.HistorySync) { + id := atomic.AddInt32(&historySyncID, 1) + fileName := fmt.Sprintf("%s/history-%d-%s-%d-%s.json", + config.PathStorages, + startupTime, + cli.Store.ID.String(), + id, + evt.Data.SyncType.String(), + ) + + file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + log.Errorf("Failed to open file to write history sync: %v", err) + return + } + defer file.Close() + + enc := json.NewEncoder(file) + enc.SetIndent("", " ") + if err = enc.Encode(evt.Data); err != nil { + log.Errorf("Failed to write history sync: %v", err) + return + } + + log.Infof("Wrote history sync to %s", fileName) +} + +func handleAppState(evt *events.AppState) { + log.Debugf("App state event: %+v / %+v", evt.Index, evt.SyncActionValue) +} diff --git a/src/pkg/whatsapp/utils.go b/src/pkg/whatsapp/utils.go index 5f3b9a6..a4e84ca 100644 --- a/src/pkg/whatsapp/utils.go +++ b/src/pkg/whatsapp/utils.go @@ -177,6 +177,18 @@ func MustLogin(waCli *whatsmeow.Client) { } } +func FormatJID(jid string) types.JID { + // Remove any :number suffix if present + if idx := strings.LastIndex(jid, ":"); idx != -1 && strings.Contains(jid, "@s.whatsapp.net") { + jid = jid[:idx] + jid[strings.Index(jid, "@s.whatsapp.net"):] + } + formattedJID, err := ParseJID(jid) + if err != nil { + return types.JID{} + } + return formattedJID +} + // isGroupJid is a helper function to check if the message is from group func isGroupJid(jid string) bool { return strings.Contains(jid, "@g.us") From a1489e70edba73af65bd7080fa77e7e5c8abc050 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sat, 1 Mar 2025 07:15:41 +0700 Subject: [PATCH 4/4] refactor: Improve WebSocket connection and message handling - Restructure WebSocket code for better readability and maintainability - Extract connection registration, unregistration, and broadcast logic into separate functions - Improve error handling and logging for WebSocket operations - Simplify message broadcasting and connection management - Add more descriptive logging for WebSocket events --- src/internal/websocket/websocket.go | 116 +++++++++++++++------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/src/internal/websocket/websocket.go b/src/internal/websocket/websocket.go index 15f337c..4c3fe3a 100644 --- a/src/internal/websocket/websocket.go +++ b/src/internal/websocket/websocket.go @@ -3,102 +3,111 @@ package websocket import ( "context" "encoding/json" + "log" + domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" "github.com/gofiber/fiber/v2" "github.com/gofiber/websocket/v2" - "log" ) -type client struct{} // Add more data to this type if needed +type client struct{} + type BroadcastMessage struct { Code string `json:"code"` Message string `json:"message"` Result any `json:"result"` } -var Clients = make(map[*websocket.Conn]client) // Note: although large maps with pointer-like types (e.g. strings) as keys are slow, using pointers themselves as keys is acceptable and fast -var Register = make(chan *websocket.Conn) -var Broadcast = make(chan BroadcastMessage) -var Unregister = make(chan *websocket.Conn) +var ( + Clients = make(map[*websocket.Conn]client) + Register = make(chan *websocket.Conn) + Broadcast = make(chan BroadcastMessage) + Unregister = make(chan *websocket.Conn) +) + +func handleRegister(conn *websocket.Conn) { + Clients[conn] = client{} + log.Println("connection registered") +} + +func handleUnregister(conn *websocket.Conn) { + delete(Clients, conn) + log.Println("connection unregistered") +} + +func broadcastMessage(message BroadcastMessage) { + marshalMessage, err := json.Marshal(message) + if err != nil { + log.Println("marshal error:", err) + return + } + + for conn := range Clients { + if err := conn.WriteMessage(websocket.TextMessage, marshalMessage); err != nil { + log.Println("write error:", err) + closeConnection(conn) + } + } +} + +func closeConnection(conn *websocket.Conn) { + if err := conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil { + log.Println("write close message error:", err) + } + if err := conn.Close(); err != nil { + log.Println("close connection error:", err) + } + delete(Clients, conn) +} func RunHub() { for { select { - case connection := <-Register: - Clients[connection] = client{} - log.Println("connection registered") + case conn := <-Register: + handleRegister(conn) + + case conn := <-Unregister: + handleUnregister(conn) case message := <-Broadcast: log.Println("message received:", message) - marshalMessage, err := json.Marshal(message) - if err != nil { - log.Println("write error:", err) - return - } - - // Send the message to all clients - for connection := range Clients { - if err := connection.WriteMessage(websocket.TextMessage, marshalMessage); err != nil { - log.Println("write error:", err) - - err := connection.WriteMessage(websocket.CloseMessage, []byte{}) - if err != nil { - log.Println("write message close error:", err) - return - } - err = connection.Close() - if err != nil { - log.Println("close error:", err) - return - } - delete(Clients, connection) - } - } - - case connection := <-Unregister: - // Remove the client from the hub - delete(Clients, connection) - - log.Println("connection unregistered") + broadcastMessage(message) } } } func RegisterRoutes(app *fiber.App, service domainApp.IAppService) { app.Use("/ws", func(c *fiber.Ctx) error { - if websocket.IsWebSocketUpgrade(c) { // Returns true if the client requested upgrade to the WebSocket protocol + if websocket.IsWebSocketUpgrade(c) { return c.Next() } return c.SendStatus(fiber.StatusUpgradeRequired) }) - app.Get("/ws", websocket.New(func(c *websocket.Conn) { - // When the function returns, unregister the client and close the connection + app.Get("/ws", websocket.New(func(conn *websocket.Conn) { defer func() { - Unregister <- c - _ = c.Close() + Unregister <- conn + _ = conn.Close() }() - // Register the client - Register <- c + Register <- conn for { - messageType, message, err := c.ReadMessage() + messageType, message, err := conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Println("read error:", err) } - return // Calls the deferred function, i.e. closes the connection on error + return } if messageType == websocket.TextMessage { - // Broadcast the received message var messageData BroadcastMessage - err := json.Unmarshal(message, &messageData) - if err != nil { - log.Println("error unmarshal message:", err) + if err := json.Unmarshal(message, &messageData); err != nil { + log.Println("unmarshal error:", err) return } + if messageData.Code == "FETCH_DEVICES" { devices, _ := service.FetchDevices(context.Background()) Broadcast <- BroadcastMessage{ @@ -107,9 +116,8 @@ func RegisterRoutes(app *fiber.App, service domainApp.IAppService) { Result: devices, } } - } else { - log.Println("websocket message received of type", messageType) + log.Println("unsupported message type:", messageType) } } }))