diff --git a/.gitignore b/.gitignore index 5c7040f..f515edb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ main main.exe *.jpe src/pkged.go -storages \ No newline at end of file +storages +.png +*.exe diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 27ce576..c278281 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: WhatsApp API MultiDevice - version: 4.2.0 + version: 4.4.0 description: This API is used for sending whatsapp via API servers: - url: http://localhost:3000 @@ -16,6 +16,8 @@ tags: description: Message manipulation (revoke/react/update). - name: group description: Group setting + - name: newsletter + description: newsletter setting paths: /app/login: get: @@ -212,6 +214,31 @@ paths: - user summary: User My List Groups responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UserGroupResponse' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorInternalServer' + /user/my/newsletters: + get: + operationId: userMyNewsletter + tags: + - user + summary: User My List Groups + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/NewsletterResponse' '500': description: Internal Server Error content: @@ -808,6 +835,50 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /message/{message_id}/read: + post: + operationId: readMessage + tags: + - message + summary: Mark as read message + parameters: + - in: path + name: message_id + schema: + type: string + required: true + description: Message ID + requestBody: + content: + application/json: + schema: + type: object + properties: + phone: + type: string + example: '62819273192397132@s.whatsapp.net' + description: Phone number with country code + required: + - phone + 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' /group: post: operationId: createGroup @@ -1042,6 +1113,40 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorInternalServer' + /newsletter/unfollow: + post: + operationId: unfollowNewsletter + tags: + - newsletter + summary: Unfollow newsletter + requestBody: + content: + application/json: + schema: + type: object + properties: + newsletter_id: + type: string + example: '120363024512399999@newsletter' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GenericResponse' + '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' components: schemas: @@ -1341,4 +1446,242 @@ components: results: type: object example: null - description: 'additional data' \ No newline at end of file + description: 'additional data' + NewsletterResponse: + type: object + properties: + code: + type: string + example: "SUCCESS" + message: + type: string + example: "Success get list newsletter" + results: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Newsletter' + Newsletter: + type: object + properties: + id: + type: string + example: "120363144038483540@newsletter" + state: + type: object + properties: + type: + type: string + example: "active" + thread_metadata: + type: object + properties: + creation_time: + type: string + example: "1688746895" + invite: + type: string + example: "0029Va4K0PZ5a245NkngBA2M" + name: + type: object + properties: + text: + type: string + example: "WhatsApp" + id: + type: string + example: "1688746895480511" + update_time: + type: string + example: "1688746895480511" + description: + type: object + properties: + text: + type: string + example: "WhatsApp’s official channel. Follow for our latest feature launches, updates, exclusive drops and more." + id: + type: string + example: "1689653839450668" + update_time: + type: string + example: "1689653839450668" + subscribers_count: + type: string + example: "0" + verification: + type: string + example: "verified" + picture: + type: object + properties: + url: + type: string + example: "" + id: + type: string + example: "1707950960975554" + type: + type: string + example: "IMAGE" + direct_path: + type: string + example: "/v/t61.24694-24/416962407_970228831134395_8869146381947923973_n.jpg?ccb=11-4&oh=01_Q5AaIIvOIeu3l0HCZWILrmr-dGR_vXFqnhUeytw0-ojPc4hL&oe=670D95B1&_nc_sid=5e03e0&_nc_cat=110" + preview: + type: object + properties: + url: + type: string + example: "" + id: + type: string + example: "1707950960975554" + type: + type: string + example: "PREVIEW" + direct_path: + type: string + example: "/v/t61.24694-24/416962407_970228831134395_8869146381947923973_n.jpg?stp=dst-jpg_s192x192&ccb=11-4&oh=01_Q5AaIHO-DQklqm3q3awF7xwji_WAn9DkgZASQA0B2Ct0qbSa&oe=670D95B1&_nc_sid=5e03e0&_nc_cat=110" + settings: + type: object + properties: + reaction_codes: + type: object + properties: + value: + type: string + example: "ALL" + viewer_metadata: + type: object + properties: + mute: + type: string + example: "off" + role: + type: string + example: "subscriber" + GroupResponse: + type: object + properties: + code: + type: string + example: "SUCCESS" + message: + type: string + example: "Success get list groups" + results: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Group' + Group: + type: object + properties: + JID: + type: string + example: "120363347168689807@g.us" + OwnerJID: + type: string + example: "6288228744537@s.whatsapp.net" + Name: + type: string + example: "Example Group" + NameSetAt: + type: string + format: date-time + example: "2024-10-11T21:27:29+07:00" + NameSetBy: + type: string + example: "6288228744537@s.whatsapp.net" + Topic: + type: string + example: "" + TopicID: + type: string + example: "" + TopicSetAt: + type: string + format: date-time + example: "0001-01-01T00:00:00Z" + TopicSetBy: + type: string + example: "" + TopicDeleted: + type: boolean + example: false + IsLocked: + type: boolean + example: false + IsAnnounce: + type: boolean + example: false + AnnounceVersionID: + type: string + example: "1728656849439709" + IsEphemeral: + type: boolean + example: false + DisappearingTimer: + type: integer + example: 0 + IsIncognito: + type: boolean + example: false + IsParent: + type: boolean + example: false + DefaultMembershipApprovalMode: + type: string + example: "" + LinkedParentJID: + type: string + example: "" + IsDefaultSubGroup: + type: boolean + example: false + IsJoinApprovalRequired: + type: boolean + example: false + GroupCreated: + type: string + format: date-time + example: "2024-10-11T21:27:29+07:00" + ParticipantVersionID: + type: string + example: "1728656849439790" + Participants: + type: array + items: + $ref: '#/components/schemas/Participant' + MemberAddMode: + type: string + example: "admin_add" + + Participant: + type: object + properties: + JID: + type: string + example: "6288228744537@s.whatsapp.net" + LID: + type: string + example: "20036609675500@lid" + IsAdmin: + type: boolean + example: true + IsSuperAdmin: + type: boolean + example: true + DisplayName: + type: string + example: "" + Error: + type: integer + example: 0 + AddRequest: + type: string + example: null \ No newline at end of file diff --git a/readme.md b/readme.md index 7f45f3d..4052b5c 100644 --- a/readme.md +++ b/readme.md @@ -112,7 +112,8 @@ You can fork or edit this source code ! | ✅ | Devices | GET | /app/devices | | ✅ | User Info | GET | /user/info | | ✅ | User Avatar | GET | /user/avatar | -| ✅ | User My Group List | GET | /user/my/groups | +| ✅ | User My Groups | GET | /user/my/groups | +| ✅ | User My Newsletter | GET | /user/my/newsletters | | ✅ | User My Privacy Setting | GET | /user/my/privacy | | ✅ | Send Message | POST | /send/message | | ✅ | Send Image | POST | /send/image | @@ -127,6 +128,7 @@ You can fork or edit this source code ! | ✅ | React Message | POST | /message/:message_id/reaction | | ✅ | Delete Message | POST | /message/:message_id/delete | | ✅ | Edit Message | POST | /message/:message_id/update | +| ✅ | Read Message (DM) | POST | /message/:message_id/read | | ❌ | Star message | POST | /message/:message_id/star | | ✅ | Join Group With Link | POST | /group/join-with-link | | ✅ | Leave Group | POST | /group/leave | @@ -135,6 +137,7 @@ You can fork or edit this source code ! | ✅ | Remove Participant in Group | POST | /group/participants/remove | | ✅ | Promote Participant in Group | POST | /group/participants/promote | | ✅ | Demote Participant in Group | POST | /group/participants/demote | +| ✅ | Unfollow Newsletter | POST | /newsletter/unfollow | ``` ✅ = Available @@ -145,7 +148,7 @@ You can fork or edit this source code ! | Description | Image | |--------------------|------------------------------------------------------------------------------------------| -| Homepage |  | +| Homepage |  | | Login |  | | Login With Code |  | | Send Message |  | @@ -167,6 +170,7 @@ You can fork or edit this source code ! | Auto Reply |  | | Basic Auth Prompt |  | | Manage Participant |  | +| My Newsletter |  | ### Mac OS NOTE diff --git a/src/cmd/root.go b/src/cmd/root.go index e5b0406..b93c224 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -113,6 +113,7 @@ func runRest(_ *cobra.Command, _ []string) { userService := services.NewUserService(cli) messageService := services.NewMessageService(cli) groupService := services.NewGroupService(cli) + newsletterService := services.NewNewsletterService(cli) // Rest rest.InitRestApp(app, appService) @@ -120,6 +121,7 @@ func runRest(_ *cobra.Command, _ []string) { rest.InitRestUser(app, userService) rest.InitRestMessage(app, messageService) rest.InitRestGroup(app, groupService) + rest.InitRestNewsletter(app, newsletterService) app.Get("/", func(c *fiber.Ctx) error { return c.Render("views/index", fiber.Map{ diff --git a/src/config/settings.go b/src/config/settings.go index 348ea92..ae47627 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -7,7 +7,7 @@ import ( ) var ( - AppVersion = "v4.18.0" + AppVersion = "v4.21.0" AppPort = "3000" AppDebug = false AppOs = "AldinoKemal" diff --git a/src/domains/message/message.go b/src/domains/message/message.go index 282781f..0de494b 100644 --- a/src/domains/message/message.go +++ b/src/domains/message/message.go @@ -3,6 +3,7 @@ package message import "context" type IMessageService interface { + MarkAsRead(ctx context.Context, request MarkAsReadRequest) (response GenericResponse, 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) @@ -35,3 +36,8 @@ type UpdateMessageRequest struct { Message string `json:"message" form:"message"` Phone string `json:"phone" form:"phone"` } + +type MarkAsReadRequest struct { + MessageID string `json:"message_id" uri:"message_id"` + Phone string `json:"phone" form:"phone"` +} diff --git a/src/domains/newsletter/newsletter.go b/src/domains/newsletter/newsletter.go new file mode 100644 index 0000000..16b1452 --- /dev/null +++ b/src/domains/newsletter/newsletter.go @@ -0,0 +1,11 @@ +package newsletter + +import "context" + +type INewsletterService interface { + Unfollow(ctx context.Context, request UnfollowRequest) (err error) +} + +type UnfollowRequest struct { + NewsletterID string `json:"newsletter_id" form:"newsletter_id"` +} diff --git a/src/domains/user/account.go b/src/domains/user/account.go index ad23e04..d424755 100644 --- a/src/domains/user/account.go +++ b/src/domains/user/account.go @@ -48,3 +48,7 @@ type MyPrivacySettingResponse struct { type MyListGroupsResponse struct { Data []types.GroupInfo `json:"data"` } + +type MyListNewsletterResponse struct { + Data []types.NewsletterMetadata `json:"data"` +} diff --git a/src/domains/user/user.go b/src/domains/user/user.go index dabca89..feb3b78 100644 --- a/src/domains/user/user.go +++ b/src/domains/user/user.go @@ -8,5 +8,6 @@ type IUserService interface { Info(ctx context.Context, request InfoRequest) (response InfoResponse, err error) Avatar(ctx context.Context, request AvatarRequest) (response AvatarResponse, err error) MyListGroups(ctx context.Context) (response MyListGroupsResponse, err error) + MyListNewsletter(ctx context.Context) (response MyListNewsletterResponse, err error) MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error) } diff --git a/src/go.mod b/src/go.mod index a01b4a3..70139fb 100644 --- a/src/go.mod +++ b/src/go.mod @@ -13,20 +13,20 @@ require ( github.com/gofiber/template/html/v2 v2.1.2 github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 - github.com/mattn/go-sqlite3 v1.14.23 + 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/stretchr/testify v1.9.0 - github.com/valyala/fasthttp v1.56.0 + github.com/valyala/fasthttp v1.57.0 go.mau.fi/libsignal v0.1.1 - go.mau.fi/whatsmeow v0.0.0-20240927134544-69ba055bef0f - google.golang.org/protobuf v1.34.2 + go.mau.fi/whatsmeow v0.0.0-20241027175758-cd900353e4a7 + google.golang.org/protobuf v1.35.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect 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 @@ -35,7 +35,7 @@ require ( github.com/gofiber/utils v1.1.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.10 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -47,11 +47,11 @@ require ( 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.8.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/image v0.20.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect + go.mau.fi/util v0.8.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/image v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.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 f5cc04c..718ce15 100644 --- a/src/go.sum +++ b/src/go.sum @@ -6,6 +6,8 @@ github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbav github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +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.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -47,6 +49,8 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2 github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -64,6 +68,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -95,8 +101,11 @@ github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8 github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U= github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI= +github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg= +github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= 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/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= @@ -104,23 +113,33 @@ go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws= go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0= go.mau.fi/util v0.8.0 h1:MiSny8jgQq4XtCLAT64gDJhZVhqiDeMVIEBDFVw+M0g= go.mau.fi/util v0.8.0/go.mod h1:1Ixb8HWoVbl3rT6nAX6nV4iMkzn7KU/KXwE0Rn5RmsQ= +go.mau.fi/util v0.8.1 h1:Ga43cz6esQBYqcjZ/onRoVnYWoUwjWbsxVeJg2jOTSo= +go.mau.fi/util v0.8.1/go.mod h1:T1u/rD2rzidVrBLyaUdPpZiJdP/rsyi+aTzn0D+Q6wc= go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 h1:Aa4uov0rM0SQQ7Fc/TZZpmQEGksie2SVTv/UuCJwViI= go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4= go.mau.fi/whatsmeow v0.0.0-20240911102933-bb3364aa3986 h1:7X+3826qoRBHPCtxY89tqMcYEsi9+OuWE6hHZfRc0qI= go.mau.fi/whatsmeow v0.0.0-20240911102933-bb3364aa3986/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4= go.mau.fi/whatsmeow v0.0.0-20240927134544-69ba055bef0f h1:+gT0NSk50HaAagtyj6J/a/QiNN34FJU+Vm1QyppV8Ec= go.mau.fi/whatsmeow v0.0.0-20240927134544-69ba055bef0f/go.mod h1:UvaXcdb8y5Mryj2LSXAMw7u4/exnWJIXn8Gvpmf6ndI= +go.mau.fi/whatsmeow v0.0.0-20241009112614-70d73b690a8d h1:0OV2Ula2IGaoHVfvv7ns+Gn3xGT0SHn5yDecJBB8FQY= +go.mau.fi/whatsmeow v0.0.0-20241009112614-70d73b690a8d/go.mod h1:UvaXcdb8y5Mryj2LSXAMw7u4/exnWJIXn8Gvpmf6ndI= +go.mau.fi/whatsmeow v0.0.0-20241027175758-cd900353e4a7 h1:0qujZcpt0G1QFCrTgrXvnVg6yQU1IG1VbmHRHkWV0iY= +go.mau.fi/whatsmeow v0.0.0-20241027175758-cd900353e4a7/go.mod h1:UvaXcdb8y5Mryj2LSXAMw7u4/exnWJIXn8Gvpmf6ndI= 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= +golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= 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= @@ -132,6 +151,8 @@ golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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= @@ -150,6 +171,8 @@ golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.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= @@ -168,6 +191,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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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 c14d223..22086b5 100644 --- a/src/internal/rest/message.go +++ b/src/internal/rest/message.go @@ -18,6 +18,7 @@ func InitRestMessage(app *fiber.App, service domainMessage.IMessageService) Mess app.Post("/message/:message_id/revoke", rest.RevokeMessage) app.Post("/message/:message_id/delete", rest.DeleteMessage) app.Post("/message/:message_id/update", rest.UpdateMessage) + app.Post("/message/:message_id/read", rest.MarkAsRead) return rest } @@ -96,3 +97,22 @@ func (controller *Message) ReactMessage(c *fiber.Ctx) error { Results: response, }) } + +func (controller *Message) MarkAsRead(c *fiber.Ctx) error { + var request message.MarkAsReadRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + request.MessageID = c.Params("message_id") + whatsapp.SanitizePhone(&request.Phone) + + response, err := controller.Service.MarkAsRead(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: response.Status, + Results: response, + }) +} diff --git a/src/internal/rest/newsletter.go b/src/internal/rest/newsletter.go new file mode 100644 index 0000000..0386e9f --- /dev/null +++ b/src/internal/rest/newsletter.go @@ -0,0 +1,32 @@ +package rest + +import ( + domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/gofiber/fiber/v2" +) + +type Newsletter struct { + Service domainNewsletter.INewsletterService +} + +func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterService) Newsletter { + rest := Newsletter{Service: service} + app.Post("/newsletter/unfollow", rest.Unfollow) + return rest +} + +func (controller *Newsletter) Unfollow(c *fiber.Ctx) error { + var request domainNewsletter.UnfollowRequest + err := c.BodyParser(&request) + utils.PanicIfNeeded(err) + + err = controller.Service.Unfollow(c.UserContext(), request) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success unfollow newsletter", + }) +} diff --git a/src/internal/rest/user.go b/src/internal/rest/user.go index dd01f5a..6621bb2 100644 --- a/src/internal/rest/user.go +++ b/src/internal/rest/user.go @@ -17,6 +17,7 @@ func InitRestUser(app *fiber.App, service domainUser.IUserService) User { app.Get("/user/avatar", rest.UserAvatar) app.Get("/user/my/privacy", rest.UserMyPrivacySetting) app.Get("/user/my/groups", rest.UserMyListGroups) + app.Get("/user/my/newsletters", rest.UserMyListNewsletter) return rest } @@ -80,3 +81,15 @@ func (controller *User) UserMyListGroups(c *fiber.Ctx) error { Results: response, }) } + +func (controller *User) UserMyListNewsletter(c *fiber.Ctx) error { + response, err := controller.Service.MyListNewsletter(c.UserContext()) + utils.PanicIfNeeded(err) + + return c.JSON(utils.ResponseData{ + Status: 200, + Code: "SUCCESS", + Message: "Success get list newsletter", + Results: response, + }) +} diff --git a/src/pkg/whatsapp/whatsapp.go b/src/pkg/whatsapp/whatsapp.go index adda1d7..4640474 100644 --- a/src/pkg/whatsapp/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -107,17 +107,19 @@ func ParseJID(arg string) (types.JID, error) { } if !strings.ContainsRune(arg, '@') { return types.NewJID(arg, types.DefaultUserServer), nil - } else { - recipient, err := types.ParseJID(arg) - if err != nil { - fmt.Printf("invalid JID %s: %v", arg, err) - return recipient, pkgError.ErrInvalidJID - } else if recipient.User == "" { - fmt.Printf("invalid JID %v: no server specified", arg) - return recipient, pkgError.ErrInvalidJID - } - return recipient, nil } + + recipient, err := types.ParseJID(arg) + if err != nil { + fmt.Printf("invalid JID %s: %v", arg, err) + return recipient, pkgError.ErrInvalidJID + } + + if recipient.User == "" { + fmt.Printf("invalid JID %v: no server specified", arg) + return recipient, pkgError.ErrInvalidJID + } + return recipient, nil } func IsOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { @@ -461,12 +463,12 @@ func ExtractMedia(storageLocation string, mediaFile whatsmeow.DownloadableMessag logrus.Info("Skip download because data is nil") return extractedMedia, nil } - + data, err := cli.Download(mediaFile) if err != nil { return extractedMedia, err } - + switch media := mediaFile.(type) { case *waE2E.ImageMessage: extractedMedia.MimeType = media.GetMimetype() @@ -482,19 +484,19 @@ func ExtractMedia(storageLocation string, mediaFile whatsmeow.DownloadableMessag extractedMedia.MimeType = media.GetMimetype() extractedMedia.Caption = media.GetCaption() } - + var extension string if ext, err := mime.ExtensionsByType(extractedMedia.MimeType); err != nil && len(ext) > 0 { extension = ext[0] } else if parts := strings.Split(extractedMedia.MimeType, "/"); len(parts) > 1 { extension = "." + parts[len(parts)-1] } - + extractedMedia.MediaPath = fmt.Sprintf("%s/%d-%s%s", storageLocation, time.Now().Unix(), uuid.NewString(), extension) err = os.WriteFile(extractedMedia.MediaPath, data, 0600) if err != nil { return extractedMedia, err } - } + } return extractedMedia, nil } diff --git a/src/services/message.go b/src/services/message.go index 9717e23..2e40aa0 100644 --- a/src/services/message.go +++ b/src/services/message.go @@ -7,6 +7,7 @@ import ( domainMessage "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/message" "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" + "github.com/sirupsen/logrus" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/proto/waCommon" @@ -27,6 +28,32 @@ func NewMessageService(waCli *whatsmeow.Client) domainMessage.IMessageService { } } +func (service serviceMessage) MarkAsRead(ctx context.Context, request domainMessage.MarkAsReadRequest) (response domainMessage.GenericResponse, err error) { + if err = validations.ValidateMarkAsRead(ctx, request); err != nil { + return response, err + } + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err + } + + ids := []types.MessageID{request.MessageID} + if err = service.WaCli.MarkRead(ids, time.Now(), dataWaRecipient, *service.WaCli.Store.ID); err != nil { + return response, err + } + + logrus.Info(map[string]interface{}{ + "phone": request.Phone, + "message_id": request.MessageID, + "chat": dataWaRecipient.String(), + "sender": service.WaCli.Store.ID.String(), + }) + + response.MessageID = request.MessageID + response.Status = fmt.Sprintf("Mark as read success %s", request.MessageID) + return response, nil +} + 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 diff --git a/src/services/newsletter.go b/src/services/newsletter.go new file mode 100644 index 0000000..0afcf42 --- /dev/null +++ b/src/services/newsletter.go @@ -0,0 +1,32 @@ +package services + +import ( + "context" + domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" + "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" + "go.mau.fi/whatsmeow" +) + +type newsletterService struct { + WaCli *whatsmeow.Client +} + +func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterService { + return &newsletterService{ + WaCli: waCli, + } +} + +func (service newsletterService) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) { + if err = validations.ValidateUnfollowNewsletter(ctx, request); err != nil { + return err + } + + JID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.NewsletterID) + if err != nil { + return err + } + + return service.WaCli.UnfollowNewsletter(JID) +} diff --git a/src/services/send.go b/src/services/send.go index 08fb2f8..f465d0e 100644 --- a/src/services/send.go +++ b/src/services/send.go @@ -17,6 +17,7 @@ import ( "github.com/valyala/fasthttp" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" "net/http" "os" @@ -157,7 +158,7 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima if err != nil { return response, err } - uploadedImage, err := service.WaCli.Upload(context.Background(), dataWaImage, whatsmeow.MediaImage) + uploadedImage, err := service.uploadMedia(ctx, whatsmeow.MediaImage, dataWaImage, dataWaRecipient) if err != nil { fmt.Printf("failed to upload file: %v", err) return response, err @@ -209,7 +210,7 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File fileMimeType := http.DetectContentType(fileBytes) // Send to WA server - uploadedFile, err := service.WaCli.Upload(context.Background(), fileBytes, whatsmeow.MediaDocument) + uploadedFile, err := service.uploadMedia(ctx, whatsmeow.MediaDocument, fileBytes, dataWaRecipient) if err != nil { fmt.Printf("Failed to upload file: %v", err) return response, err @@ -311,7 +312,7 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid if err != nil { return response, err } - uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) + uploaded, err := service.uploadMedia(ctx, whatsmeow.MediaVideo, dataWaVideo, dataWaRecipient) if err != nil { return response, pkgError.InternalServerError(fmt.Sprintf("Failed to upload file: %v", err)) } @@ -448,7 +449,7 @@ func (service serviceSend) SendAudio(ctx context.Context, request domainSend.Aud autioBytes := helpers.MultipartFormFileHeaderToBytes(request.Audio) audioMimeType := http.DetectContentType(autioBytes) - audioUploaded, err := service.WaCli.Upload(ctx, autioBytes, whatsmeow.MediaAudio) + audioUploaded, err := service.uploadMedia(ctx, whatsmeow.MediaAudio, autioBytes, dataWaRecipient) if err != nil { err = pkgError.WaUploadMediaError(fmt.Sprintf("Failed to upload audio: %v", err)) return response, err @@ -496,7 +497,7 @@ func (service serviceSend) SendPoll(ctx context.Context, request domainSend.Poll return response, nil } -func (service serviceSend) getMentionFromText(ctx context.Context, messages string) (result []string) { +func (service serviceSend) getMentionFromText(_ context.Context, messages string) (result []string) { mentions := utils.ContainsMention(messages) for _, mention := range mentions { // Get JID from phone number @@ -507,3 +508,12 @@ func (service serviceSend) getMentionFromText(ctx context.Context, messages stri return result } + +func (service serviceSend) uploadMedia(ctx context.Context, mediaType whatsmeow.MediaType, media []byte, recipient types.JID) (uploaded whatsmeow.UploadResponse, err error) { + if recipient.Server == types.NewsletterServer { + uploaded, err = service.WaCli.UploadNewsletter(ctx, media, mediaType) + } else { + uploaded, err = service.WaCli.Upload(ctx, media, mediaType) + } + return uploaded, err +} diff --git a/src/services/user.go b/src/services/user.go index a6e7b8a..19d0652 100644 --- a/src/services/user.go +++ b/src/services/user.go @@ -127,6 +127,20 @@ func (service userService) MyListGroups(_ context.Context) (response domainUser. return response, nil } +func (service userService) MyListNewsletter(_ context.Context) (response domainUser.MyListNewsletterResponse, err error) { + whatsapp.MustLogin(service.WaCli) + + datas, err := service.WaCli.GetSubscribedNewsletters() + if err != nil { + return + } + fmt.Printf("%+v\n", datas) + for _, data := range datas { + response.Data = append(response.Data, *data) + } + return response, nil +} + func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) { whatsapp.MustLogin(service.WaCli) diff --git a/src/validations/message_validation.go b/src/validations/message_validation.go index 6e268fd..358cce2 100644 --- a/src/validations/message_validation.go +++ b/src/validations/message_validation.go @@ -8,6 +8,20 @@ import ( validation "github.com/go-ozzo/ozzo-validation/v4" ) +func ValidateMarkAsRead(ctx context.Context, request domainMessage.MarkAsReadRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.MessageID, validation.Required), + validation.Field(&request.Phone, validation.Required), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil + +} + func ValidateRevokeMessage(ctx context.Context, request domainMessage.RevokeRequest) error { err := validation.ValidateStructWithContext(ctx, &request, validation.Field(&request.Phone, validation.Required), diff --git a/src/validations/newsletter_validation.go b/src/validations/newsletter_validation.go new file mode 100644 index 0000000..635d7c7 --- /dev/null +++ b/src/validations/newsletter_validation.go @@ -0,0 +1,20 @@ +package validations + +import ( + "context" + domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +func ValidateUnfollowNewsletter(ctx context.Context, request domainNewsletter.UnfollowRequest) error { + err := validation.ValidateStructWithContext(ctx, &request, + validation.Field(&request.NewsletterID, validation.Required), + ) + + if err != nil { + return pkgError.ValidationError(err.Error()) + } + + return nil +} diff --git a/src/views/components/AccountAvatar.js b/src/views/components/AccountAvatar.js index 956a79c..9f286c4 100644 --- a/src/views/components/AccountAvatar.js +++ b/src/views/components/AccountAvatar.js @@ -1,10 +1,14 @@ -import { NumberFormatLocale } from './funcoes.js' +import { NumberFormatLocale } from './funcoes.js'; +import FormRecipient from "./generic/FormRecipient.js"; export default { name: 'AccountAvatar', + components: { + FormRecipient + }, data() { return { - type: 'user', + type: window.TYPEUSER, phone: '', image: null, loading: false, @@ -14,7 +18,7 @@ export default { }, computed: { phone_id() { - return this.type === 'user' ? `${NumberFormatLocale(this.phone)}@${window.TYPEUSER}` : `${NumberFormatLocale(this.phone)}@${window.TYPEGROUP}` + return NumberFormatLocale(this.phone) + this.type; } }, methods: { @@ -47,7 +51,7 @@ export default { handleReset() { this.phone = ''; this.image = null; - this.type = 'user'; + this.type = window.TYPEUSER; } }, template: ` @@ -68,19 +72,7 @@ export default {