diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 60533b8..e958ac1 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 3.11.0 + version: 4.0.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -200,7 +200,7 @@ paths: summary: Send Message requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -427,7 +427,7 @@ paths: summary: Send Contact requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -470,7 +470,7 @@ paths: summary: Send Link requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -513,7 +513,7 @@ paths: summary: Send Location requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -618,7 +618,49 @@ paths: description: Message ID requestBody: content: - multipart/form-data: + application/json: + schema: + type: object + properties: + phone: + type: string + example: '6289685024051@s.whatsapp.net' + description: Phone number with country code + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SendResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBadRequest' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' + /message/{message_id}/delete: + post: + operationId: deleteMessage + tags: + - message + summary: Delete Message + parameters: + - in: path + name: message_id + schema: + type: string + required: true + description: Message ID + requestBody: + content: + application/json: schema: type: object properties: @@ -660,7 +702,7 @@ paths: description: Message ID requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -840,7 +882,7 @@ paths: summary: Join group with link requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: @@ -874,7 +916,7 @@ paths: summary: Leave group requestBody: content: - multipart/form-data: + application/json: schema: type: object properties: diff --git a/readme.md b/readme.md index 3ce80e8..efc4f54 100644 --- a/readme.md +++ b/readme.md @@ -92,38 +92,42 @@ docker run --detach --publish=3000:3000 --name=whatsapp --restart=always --volum You can fork or edit this source code ! ### Current API + - [Api Specification Document](https://bump.sh/aldinokemal/doc/go-whatsapp-web-multidevice) -- You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API or paste to [SwaggerEditor](https://editor.swagger.io). +- You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API or paste + to [SwaggerEditor](https://editor.swagger.io). - Furthermore you can generate HTTP Client from this API using [openapi-generator](https://openapi-generator.tech/#try) -| Feature | Menu | Method | URL | -|---------|------------------------------|--------|-----------------------------| -| ✅ | Login | GET | /app/login | -| ✅ | Logout | GET | /app/logout | -| ✅ | Reconnect | GET | /app/reconnect | -| ✅ | User Info | GET | /user/info | -| ✅ | User Avatar | GET | /user/avatar | -| ✅ | User My Group List | GET | /user/my/groups | -| ✅ | User My Privacy Setting | GET | /user/my/privacy | -| ✅ | Send Message | POST | /send/message | -| ✅ | Send Image | POST | /send/image | -| ✅ | Send Audio | POST | /send/audio | -| ✅ | Send File | POST | /send/file | -| ✅ | Send Video | POST | /send/video | -| ✅ | Send Contact | POST | /send/contact | -| ✅ | Send Link | POST | /send/link | -| ✅ | Send Location | POST | /send/location | -| ✅ | Send Poll / Vote | POST | /send/poll | -| ✅ | Revoke Message | POST | /message/:message_id/revoke | -| ✅ | React Message | POST | /message/:message_id/react | -| ✅ | Edit Message | POST | /message/:message_id/update | -| ✅ | Join Group With Link | POST | /group/join-with-link | -| ✅ | Leave Group | POST | /group/leave | -| ✅ | Create Group | POST | /group | -| ✅ | Add Participants in Group | POST | /group/participants | -| ❌ | Remove Participant in Group | DELETE | /group/participants | -| ❌ | Promote Participant in Group | POST | /group/participants/promote | -| ❌ | Demote Participant in Group | POST | /group/participants/demote | +| Feature | Menu | Method | URL | +|---------|------------------------------|--------|-------------------------------| +| ✅ | Login | GET | /app/login | +| ✅ | Logout | GET | /app/logout | +| ✅ | Reconnect | GET | /app/reconnect | +| ✅ | Devices | GET | /app/devices | +| ✅ | User Info | GET | /user/info | +| ✅ | User Avatar | GET | /user/avatar | +| ✅ | User My Group List | GET | /user/my/groups | +| ✅ | User My Privacy Setting | GET | /user/my/privacy | +| ✅ | Send Message | POST | /send/message | +| ✅ | Send Image | POST | /send/image | +| ✅ | Send Audio | POST | /send/audio | +| ✅ | Send File | POST | /send/file | +| ✅ | Send Video | POST | /send/video | +| ✅ | Send Contact | POST | /send/contact | +| ✅ | Send Link | POST | /send/link | +| ✅ | Send Location | POST | /send/location | +| ✅ | Send Poll / Vote | POST | /send/poll | +| ✅ | Revoke Message | POST | /message/:message_id/revoke | +| ✅ | React Message | POST | /message/:message_id/reaction | +| ✅ | Delete Message | POST | /message/:message_id/delete | +| ✅ | Edit Message | POST | /message/:message_id/update | +| ✅ | Join Group With Link | POST | /group/join-with-link | +| ✅ | Leave Group | POST | /group/leave | +| ✅ | Create Group | POST | /group | +| ✅ | Add Participants in Group | POST | /group/participants | +| ❌ | Remove Participant in Group | DELETE | /group/participants | +| ❌ | Promote Participant in Group | POST | /group/participants/promote | +| ❌ | Demote Participant in Group | POST | /group/participants/demote | ``` ✅ = Available @@ -132,25 +136,26 @@ You can fork or edit this source code ! ### App User Interface -1. Homepage ![Homepage](https://i.ibb.co/Wn3H1L9/homepage.png) -2. Login ![Login](https://i.ibb.co/jkcB15R/login.png?v=1) -3. Send Message ![Send Message](https://i.ibb.co/rc3NXMX/send-message.png?v1) -4. Send Image ![Send Image](https://i.ibb.co/BcFL3SD/send-image.png?v1) -5. Send File ![Send File](https://i.ibb.co/f4yxjpp/send-file.png) -6. Send Video ![Send Video](https://i.ibb.co/PrD3P51/send-video.png) -7. Send Contact ![Send Contact](https://i.ibb.co/4810H7N/send-contact.png) -8. Send Location ![Send Location](https://i.ibb.co/TWsy09G/send-location.png) -9. Send Audio ![Send Location](https://i.ibb.co/p1wL4wh/Send-Audio.png) -10. Send Poll ![Send Poll](https://i.ibb.co/mq2fGHz/send-poll.png) -11. Revoke Message ![Revoke Message](https://i.ibb.co/yswhvQY/revoke.png?v1) -12. Reaction Message ![Revoke Message](https://i.ibb.co/BfHgSHG/react-message.png) -13. Edit Message ![Edit Message](https://i.ibb.co/kXfpqJw/update-message.png) -14. User Info ![User Info](https://i.ibb.co/3zjX6Cz/user-info.png?v=1) -15. User Avatar ![User Avatar](https://i.ibb.co/ZmJZ4ZW/search-avatar.png?v=1) -16. My Privacy ![My Privacy](https://i.ibb.co/Cw1sMQz/my-privacy.png) -17. My Group ![My Group](https://i.ibb.co/WB268Xy/list-group.png) -18. Auto Reply ![Auto Reply](https://i.ibb.co/D4rTytX/IMG-20220517-162500.jpg) -19. Basic Auth Prompt ![Basic Auth](https://i.ibb.co/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) +1. Homepage ![Homepage](https://i.ibb.co.com/681JTHK/image.png) +2. Login ![Login](https://i.ibb.co.com/jkcB15R/login.png?v=1) +3. Send Message ![Send Message](https://i.ibb.co.com/rc3NXMX/send-message.png?v1) +4. Send Image ![Send Image](https://i.ibb.co.com/BcFL3SD/send-image.png?v1) +5. Send File ![Send File](https://i.ibb.co.com/f4yxjpp/send-file.png) +6. Send Video ![Send Video](https://i.ibb.co.com/PrD3P51/send-video.png) +7. Send Contact ![Send Contact](https://i.ibb.co.com/4810H7N/send-contact.png) +8. Send Location ![Send Location](https://i.ibb.co.com/TWsy09G/send-location.png) +9. Send Audio ![Send Location](https://i.ibb.co.com/p1wL4wh/Send-Audio.png) +10. Send Poll ![Send Poll](https://i.ibb.co.com/mq2fGHz/send-poll.png) +11. Revoke Message ![Revoke Message](https://i.ibb.co.com/yswhvQY/revoke.png?v1) +12. Delete Message ![Delete Message](https://i.ibb.co.com/F70SZ84/image.png) +13. Reaction Message ![Revoke Message](https://i.ibb.co.com/BfHgSHG/react-message.png) +14. Edit Message ![Edit Message](https://i.ibb.co.com/kXfpqJw/update-message.png) +15. User Info ![User Info](https://i.ibb.co.com/3zjX6Cz/user-info.png?v=1) +16. User Avatar ![User Avatar](https://i.ibb.co.com/ZmJZ4ZW/search-avatar.png?v=1) +17. My Privacy ![My Privacy](https://i.ibb.co.com/Cw1sMQz/my-privacy.png) +18. My Group ![My Group](https://i.ibb.co.com/WB268Xy/list-group.png) +19. Auto Reply ![Auto Reply](https://i.ibb.co.com/D4rTytX/IMG-20220517-162500.jpg) +20. Basic Auth Prompt ![Basic Auth](https://i.ibb.co.com/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) ### Mac OS NOTE diff --git a/src/config/settings.go b/src/config/settings.go index 6e40818..9d9458e 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -5,7 +5,7 @@ import ( ) var ( - AppVersion = "v4.13.0" + AppVersion = "v4.14.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/domains/message/message.go b/src/domains/message/message.go index a65a44f..282781f 100644 --- a/src/domains/message/message.go +++ b/src/domains/message/message.go @@ -3,7 +3,35 @@ package message import "context" type IMessageService interface { - ReactMessage(ctx context.Context, request ReactionRequest) (response ReactionResponse, err error) - RevokeMessage(ctx context.Context, request RevokeRequest) (response RevokeResponse, err error) - UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response UpdateMessageResponse, err error) + ReactMessage(ctx context.Context, request ReactionRequest) (response GenericResponse, err error) + RevokeMessage(ctx context.Context, request RevokeRequest) (response GenericResponse, err error) + UpdateMessage(ctx context.Context, request UpdateMessageRequest) (response GenericResponse, err error) + DeleteMessage(ctx context.Context, request DeleteRequest) (err error) +} + +type GenericResponse struct { + MessageID string `json:"message_id"` + Status string `json:"status"` +} + +type RevokeRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Phone string `json:"phone" form:"phone"` +} + +type DeleteRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Phone string `json:"phone" form:"phone"` +} + +type ReactionRequest struct { + MessageID string `json:"message_id" form:"message_id"` + Phone string `json:"phone" form:"phone"` + Emoji string `json:"emoji" form:"emoji"` +} + +type UpdateMessageRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Message string `json:"message" form:"message"` + Phone string `json:"phone" form:"phone"` } diff --git a/src/domains/message/reaction.go b/src/domains/message/reaction.go deleted file mode 100644 index 45a5c8f..0000000 --- a/src/domains/message/reaction.go +++ /dev/null @@ -1,12 +0,0 @@ -package message - -type ReactionRequest struct { - MessageID string `json:"message_id" form:"message_id"` - Phone string `json:"phone" form:"phone"` - Emoji string `json:"emoji" form:"emoji"` -} - -type ReactionResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/domains/message/revoke.go b/src/domains/message/revoke.go deleted file mode 100644 index e7567e9..0000000 --- a/src/domains/message/revoke.go +++ /dev/null @@ -1,11 +0,0 @@ -package message - -type RevokeRequest struct { - MessageID string `json:"message_id" uri:"message_id"` - Phone string `json:"phone" form:"phone"` -} - -type RevokeResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/domains/message/update.go b/src/domains/message/update.go deleted file mode 100644 index 5321055..0000000 --- a/src/domains/message/update.go +++ /dev/null @@ -1,12 +0,0 @@ -package message - -type UpdateMessageRequest struct { - MessageID string `json:"message_id" uri:"message_id"` - Message string `json:"message" form:"message"` - Phone string `json:"phone" form:"phone"` -} - -type UpdateMessageResponse struct { - MessageID string `json:"message_id"` - Status string `json:"status"` -} diff --git a/src/go.mod b/src/go.mod index 977e4a2..fdab821 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,7 +3,7 @@ module github.com/aldinokemal/go-whatsapp-web-multidevice go 1.21 require ( - github.com/PuerkitoBio/goquery v1.9.1 + github.com/PuerkitoBio/goquery v1.9.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 @@ -16,10 +16,10 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 - github.com/valyala/fasthttp v1.52.0 + github.com/valyala/fasthttp v1.54.0 go.mau.fi/libsignal v0.1.0 - go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c - google.golang.org/protobuf v1.33.0 + go.mau.fi/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1 + google.golang.org/protobuf v1.34.1 ) require ( @@ -28,7 +28,7 @@ require ( github.com/andybalholm/cascadia v1.3.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fasthttp/websocket v1.5.8 // indirect + github.com/fasthttp/websocket v1.5.9 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect @@ -40,16 +40,16 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/zerolog v1.32.0 // indirect + github.com/rs/zerolog v1.33.0 // indirect github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect go.mau.fi/util v0.4.2 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/image v0.15.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/image v0.16.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/src/go.sum b/src/go.sum index 7db8366..1cc3487 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.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI= github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY= +github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= +github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= @@ -20,6 +22,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8= github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0= +github.com/fasthttp/websocket v1.5.9 h1:9deGuzYcCRKjk940kNwSN6Hd14hk4zYwropm4UsUIUQ= +github.com/fasthttp/websocket v1.5.9/go.mod h1:NLzHBFur260OMuZHohOfYQwMTpR7sfSpUnuqKxMpgKA= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -67,6 +71,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= @@ -87,6 +93,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.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0= +github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= 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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -96,13 +104,19 @@ go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30= go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw= go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c h1:a5O4nqmwUWvmC+27RUdefkuy5XzMOEUqR9ji+/BcHZA= go.mau.fi/whatsmeow v0.0.0-20240327124018-350073db195c/go.mod h1:kNI5foyzqd77d5HaWc1Jico6/rxtZ/UE8nr80hIsbIk= +go.mau.fi/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1 h1:mUEEmZs1xk5QHKXjDxiAP4bYgyj8r7PaZCafHN+KMQg= +go.mau.fi/whatsmeow v0.0.0-20240523075404-7f13c31d2cb1/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -112,6 +126,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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= @@ -128,6 +144,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -146,6 +164,8 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/internal/rest/message.go b/src/internal/rest/message.go index 71e6329..c14d223 100644 --- a/src/internal/rest/message.go +++ b/src/internal/rest/message.go @@ -16,6 +16,7 @@ func InitRestMessage(app *fiber.App, service domainMessage.IMessageService) Mess rest := Message{Service: service} app.Post("/message/:message_id/reaction", rest.ReactMessage) app.Post("/message/:message_id/revoke", rest.RevokeMessage) + app.Post("/message/:message_id/delete", rest.DeleteMessage) app.Post("/message/:message_id/update", rest.UpdateMessage) return rest } @@ -39,6 +40,25 @@ func (controller *Message) RevokeMessage(c *fiber.Ctx) error { }) } +func (controller *Message) DeleteMessage(c *fiber.Ctx) error { + var request domainMessage.DeleteRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + request.MessageID = c.Params("message_id") + whatsapp.SanitizePhone(&request.Phone) + + err = controller.Service.DeleteMessage(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Message deleted successfully", + Results: nil, + }) +} + func (controller *Message) UpdateMessage(c *fiber.Ctx) error { var request domainMessage.UpdateMessageRequest err := c.BodyParser(&request) diff --git a/src/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index 97d0a66..5c01437 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -188,6 +188,8 @@ func MustLogin(waCli *whatsmeow.Client) { func handler(rawEvt interface{}) { switch evt := rawEvt.(type) { + case *events.DeleteForMe: + log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String()) case *events.AppStateSyncComplete: if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { err := cli.SendPresence(types.PresenceAvailable) diff --git a/src/services/message.go b/src/services/message.go index 9ba678a..9717e23 100644 --- a/src/services/message.go +++ b/src/services/message.go @@ -8,7 +8,10 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "go.mau.fi/whatsmeow" - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waCommon" + "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/proto/waSyncAction" "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" "time" @@ -24,7 +27,7 @@ func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageService { } } -func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.ReactionResponse, err error) { +func (service serviceMessage) ReactMessage(ctx context.Context, request message.ReactionRequest) (response message.GenericResponse, err error) { if err = validations.ValidateReactMessage(ctx, request); err != nil { return response, err } @@ -33,15 +36,15 @@ func (service serviceMessage) ReactMessage(ctx context.Context, request message. return response, err } - msg := &waProto.Message{ - ReactionMessage: &waProto.ReactionMessage{ - Key: &waProto.MessageKey{ + msg := &waE2E.Message{ + ReactionMessage: &waE2E.ReactionMessage{ + Key: &waCommon.MessageKey{ FromMe: proto.Bool(true), - Id: proto.String(request.MessageID), - RemoteJid: proto.String(dataWaRecipient.String()), + ID: proto.String(request.MessageID), + RemoteJID: proto.String(dataWaRecipient.String()), }, Text: proto.String(request.Emoji), - SenderTimestampMs: proto.Int64(time.Now().UnixMilli()), + SenderTimestampMS: proto.Int64(time.Now().UnixMilli()), }, } ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) @@ -54,7 +57,7 @@ func (service serviceMessage) ReactMessage(ctx context.Context, request message. return response, nil } -func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.RevokeResponse, err error) { +func (service serviceMessage) RevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateRevokeMessage(ctx, request); err != nil { return response, err } @@ -73,7 +76,41 @@ func (service serviceMessage) RevokeMessage(ctx context.Context, request domainM return response, nil } -func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.UpdateMessageResponse, err error) { +func (service serviceMessage) DeleteMessage(ctx context.Context, request domainMessage.DeleteRequest) (err error) { + if err = validations.ValidateDeleteMessage(ctx, request); err != nil { + return err + } + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return err + } + + isFromMe := "1" + if len(request.MessageID) > 22 { + isFromMe = "0" + } + + patchInfo := appstate.PatchInfo{ + Timestamp: time.Now(), + Type: appstate.WAPatchRegularHigh, + Mutations: []appstate.MutationInfo{{ + Index: []string{appstate.IndexDeleteMessageForMe, dataWaRecipient.String(), request.MessageID, isFromMe, service.WaCli.Store.ID.String()}, + Value: &waSyncAction.SyncActionValue{ + DeleteMessageForMeAction: &waSyncAction.DeleteMessageForMeAction{ + DeleteMedia: proto.Bool(true), + MessageTimestamp: proto.Int64(time.Now().UnixMilli()), + }, + }, + }}, + } + + if err = service.WaCli.SendAppState(patchInfo); err != nil { + return err + } + return nil +} + +func (service serviceMessage) UpdateMessage(ctx context.Context, request domainMessage.UpdateMessageRequest) (response domainMessage.GenericResponse, err error) { if err = validations.ValidateUpdateMessage(ctx, request); err != nil { return response, err } @@ -83,7 +120,7 @@ func (service serviceMessage) UpdateMessage(ctx context.Context, request domainM return response, err } - msg := &waProto.Message{Conversation: proto.String(request.Message)} + msg := &waE2E.Message{Conversation: proto.String(request.Message)} ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, service.WaCli.BuildEdit(dataWaRecipient, request.MessageID, msg)) if err != nil { return response, err diff --git a/src/services/send.go b/src/services/send.go index f5b1b6e..39e6c4b 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" "go.mau.fi/whatsmeow" - waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/proto/waE2E" "google.golang.org/protobuf/proto" "net/http" "os" @@ -46,7 +46,7 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess } // Send message - msg := &waProto.Message{Conversation: proto.String(request.Message)} + msg := &waE2E.Message{Conversation: proto.String(request.Message)} // Reply message if request.ReplyMessageID != nil && *request.ReplyMessageID != "" { @@ -59,13 +59,13 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess participantJID = firstDevice.Device } - msg = &waProto.Message{ - ExtendedTextMessage: &waProto.ExtendedTextMessage{ + msg = &waE2E.Message{ + ExtendedTextMessage: &waE2E.ExtendedTextMessage{ Text: proto.String(request.Message), - ContextInfo: &waProto.ContextInfo{ - StanzaId: request.ReplyMessageID, + ContextInfo: &waE2E.ContextInfo{ + StanzaID: request.ReplyMessageID, Participant: proto.String(participantJID), - QuotedMessage: &waProto.Message{ + QuotedMessage: &waE2E.Message{ Conversation: proto.String(request.Message), }, }, @@ -154,15 +154,15 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima return response, pkgError.InternalServerError(fmt.Sprintf("failed to read thumbnail %v", err)) } - msg := &waProto.Message{ImageMessage: &waProto.ImageMessage{ - JpegThumbnail: dataWaThumbnail, + msg := &waE2E.Message{ImageMessage: &waE2E.ImageMessage{ + JPEGThumbnail: dataWaThumbnail, Caption: proto.String(dataWaCaption), - Url: proto.String(uploadedImage.URL), + URL: proto.String(uploadedImage.URL), DirectPath: proto.String(uploadedImage.DirectPath), MediaKey: uploadedImage.MediaKey, Mimetype: proto.String(http.DetectContentType(dataWaImage)), - FileEncSha256: uploadedImage.FileEncSHA256, - FileSha256: uploadedImage.FileSHA256, + FileEncSHA256: uploadedImage.FileEncSHA256, + FileSHA256: uploadedImage.FileSHA256, FileLength: proto.Uint64(uint64(len(dataWaImage))), ViewOnce: proto.Bool(request.ViewOnce), }} @@ -205,15 +205,15 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File return response, err } - msg := &waProto.Message{DocumentMessage: &waProto.DocumentMessage{ - Url: proto.String(uploadedFile.URL), + msg := &waE2E.Message{DocumentMessage: &waE2E.DocumentMessage{ + URL: proto.String(uploadedFile.URL), Mimetype: proto.String(fileMimeType), Title: proto.String(request.File.Filename), - FileSha256: uploadedFile.FileSHA256, + FileSHA256: uploadedFile.FileSHA256, FileLength: proto.Uint64(uploadedFile.FileLength), MediaKey: uploadedFile.MediaKey, FileName: proto.String(request.File.Filename), - FileEncSha256: uploadedFile.FileEncSHA256, + FileEncSHA256: uploadedFile.FileEncSHA256, DirectPath: proto.String(uploadedFile.DirectPath), Caption: proto.String(request.Caption), }} @@ -310,19 +310,19 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid return response, err } - msg := &waProto.Message{VideoMessage: &waProto.VideoMessage{ - Url: proto.String(uploaded.URL), + msg := &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ + URL: proto.String(uploaded.URL), Mimetype: proto.String(http.DetectContentType(dataWaVideo)), Caption: proto.String(request.Caption), FileLength: proto.Uint64(uploaded.FileLength), - FileSha256: uploaded.FileSHA256, - FileEncSha256: uploaded.FileEncSHA256, + FileSHA256: uploaded.FileSHA256, + FileEncSHA256: uploaded.FileEncSHA256, MediaKey: uploaded.MediaKey, DirectPath: proto.String(uploaded.DirectPath), ViewOnce: proto.Bool(request.ViewOnce), - JpegThumbnail: dataWaThumbnail, - ThumbnailEncSha256: dataWaThumbnail, - ThumbnailSha256: dataWaThumbnail, + JPEGThumbnail: dataWaThumbnail, + ThumbnailEncSHA256: dataWaThumbnail, + ThumbnailSHA256: dataWaThumbnail, ThumbnailDirectPath: proto.String(uploaded.DirectPath), }} ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msg) @@ -353,7 +353,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD", request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone) - msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{ + msg := &waE2E.Message{ContactMessage: &waE2E.ContactMessage{ DisplayName: proto.String(request.ContactName), Vcard: proto.String(msgVCard), }} @@ -379,10 +379,10 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link getMetaDataFromURL := utils.GetMetaDataFromURL(request.Link) - msg := &waProto.Message{ExtendedTextMessage: &waProto.ExtendedTextMessage{ + msg := &waE2E.Message{ExtendedTextMessage: &waE2E.ExtendedTextMessage{ Text: proto.String(fmt.Sprintf("%s\n%s", request.Caption, request.Link)), Title: proto.String(getMetaDataFromURL.Title), - CanonicalUrl: proto.String(request.Link), + CanonicalURL: proto.String(request.Link), MatchedText: proto.String(request.Link), Description: proto.String(getMetaDataFromURL.Description), }} @@ -407,8 +407,8 @@ func (service serviceSend) SendLocation(ctx context.Context, request domainSend. } // Compose WhatsApp Proto - msg := &waProto.Message{ - LocationMessage: &waProto.LocationMessage{ + msg := &waE2E.Message{ + LocationMessage: &waE2E.LocationMessage{ DegreesLatitude: proto.Float64(utils.StrToFloat64(request.Latitude)), DegreesLongitude: proto.Float64(utils.StrToFloat64(request.Longitude)), }, @@ -444,14 +444,14 @@ func (service serviceSend) SendAudio(ctx context.Context, request domainSend.Aud return response, err } - msg := &waProto.Message{ - AudioMessage: &waProto.AudioMessage{ - Url: proto.String(audioUploaded.URL), + msg := &waE2E.Message{ + AudioMessage: &waE2E.AudioMessage{ + URL: proto.String(audioUploaded.URL), DirectPath: proto.String(audioUploaded.DirectPath), Mimetype: proto.String(audioMimeType), FileLength: proto.Uint64(audioUploaded.FileLength), - FileSha256: audioUploaded.FileSHA256, - FileEncSha256: audioUploaded.FileEncSHA256, + FileSHA256: audioUploaded.FileSHA256, + FileEncSHA256: audioUploaded.FileEncSHA256, MediaKey: audioUploaded.MediaKey, }, } diff --git a/src/validations/message_validation.go b/src/validations/message_validation.go index b7caabe..6e268fd 100644 --- a/src/validations/message_validation.go +++ b/src/validations/message_validation.go @@ -48,3 +48,16 @@ func ValidateReactMessage(ctx context.Context, request message.ReactionRequest) return nil } + +func ValidateDeleteMessage(ctx context.Context, request domainMessage.DeleteRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.Phone, validation.Required), + validation.Field(&request.MessageID, validation.Required), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil +} diff --git a/src/views/components/AccountUserInfo.js b/src/views/components/AccountUserInfo.js index 4bc2aa0..a034990 100644 --- a/src/views/components/AccountUserInfo.js +++ b/src/views/components/AccountUserInfo.js @@ -95,7 +95,7 @@ export default {
    -
  1. Nama: {{ name }}
  2. +
  3. Name: {{ name }}
  4. Status: {{ status }}
  5. Device:
      diff --git a/src/views/components/MessageDelete.js b/src/views/components/MessageDelete.js new file mode 100644 index 0000000..cb6f46d --- /dev/null +++ b/src/views/components/MessageDelete.js @@ -0,0 +1,106 @@ +export default { + name: 'DeleteMessage', + data() { + return { + type: 'user', + phone: '', + message_id: '', + loading: false, + } + }, + computed: { + phone_id() { + return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}` + } + }, + methods: { + openModal() { + $('#modalMessageDelete').modal({ + onApprove: function () { + return false; + } + }).modal('show'); + }, + async handleSubmit() { + try { + let response = await this.submitApi() + showSuccessInfo(response) + $('#modalMessageDelete').modal('hide'); + } catch (err) { + showErrorInfo(err) + } + }, + async submitApi() { + this.loading = true; + try { + const payload = {phone: this.phone_id, message: this.new_message} + + let response = await window.http.post(`/message/${this.message_id}/delete`, payload) + this.handleReset(); + return response.data.message; + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message); + } + throw new Error(error.message); + } finally { + this.loading = false; + } + }, + handleReset() { + this.type = 'user'; + this.phone = ''; + this.message_id = ''; + this.new_message = ''; + this.loading = false; + }, + }, + template: ` +
      +
      + Message +
      Delete Message
      +
      + Delete your sent message +
      +
      +
      + + + + ` +} \ No newline at end of file diff --git a/src/views/index.html b/src/views/index.html index 75723e0..85140d7 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -68,6 +68,7 @@

      Whatsapp API Multi Device ({{ .AppVersion }

+ @@ -136,6 +137,7 @@

Whatsapp API Multi Device ({{ .AppVersion } import SendLocation from "./components/SendLocation.js"; import SendAudio from "./components/SendAudio.js"; import SendPoll from "./components/SendPoll.js"; + import MessageDelete from "./components/MessageDelete.js"; import MessageUpdate from "./components/MessageUpdate.js"; import MessageReact from "./components/MessageReact.js"; import MessageRevoke from "./components/MessageRevoke.js"; @@ -170,7 +172,7 @@

Whatsapp API Multi Device ({{ .AppVersion } components: { AppLogin, AppLogout, AppReconnect, SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll, - MessageUpdate, MessageReact, MessageRevoke, + MessageDelete, MessageUpdate, MessageReact, MessageRevoke, GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants, AccountAvatar, AccountUserInfo, AccountPrivacy },