diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 27ce576..71569f6 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.3.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: @@ -1042,6 +1069,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 +1402,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..e902f69 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 | @@ -135,6 +136,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 | /group/newsletter/unfollow | ``` ✅ = Available @@ -145,7 +147,7 @@ You can fork or edit this source code ! | Description | Image | |--------------------|------------------------------------------------------------------------------------------| -| Homepage |  | +| Homepage |  | | Login |  | | Login With Code |  | | Send Message |  | @@ -167,6 +169,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/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/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 d0f4953..77e20f8 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 { 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/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/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 2e602cb..e9ab15f 100644 --- a/src/views/components/AccountAvatar.js +++ b/src/views/components/AccountAvatar.js @@ -1,8 +1,13 @@ +import FormRecipient from "./generic/FormRecipient.js"; + export default { name: 'AccountAvatar', + components: { + FormRecipient + }, data() { return { - type: 'user', + type: window.TYPEUSER, phone: '', image: null, loading: false, @@ -12,7 +17,7 @@ export default { }, computed: { phone_id() { - return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}` + return this.phone + this.type; } }, methods: { @@ -45,7 +50,7 @@ export default { handleReset() { this.phone = ''; this.image = null; - this.type = 'user'; + this.type = window.TYPEUSER; } }, template: ` @@ -66,19 +71,7 @@ export default {