Browse Source

feat: add newsletter (#204)

* feat(newsletter): add newsletter service, endpoints, and UI components

Add Newsletter service to support functionality for unfollowing newsletters
Add Newsletter REST controller and routing
Implement newsletter-related endpoints and methods in the User Service
Create UnfollowRequest for the INewsletterService interface
Add MyListNewsletterResponse to user's data fields
Add newsletter validation
Refactor JS components to support newsletter type and simplify recipient forms by moving logic to FormRecipient component
Refactor window global constants to support newsletters
Modify server to initialize and use the newsletter services
Add UI component for listing newsletters
Refactor existing components to use FormRecipient for recipient data input

* chore: update documentation

feat(openapi.yaml): add newsletter support with new paths and schemas
docs(readme.md): update API endpoints including newsletter and images
fix(openapi.yaml): correct duplicated summary text for user my newsletters

* feat: update package name

* feat: Update src/views/components/NewsletterList.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: Update src/views/components/NewsletterList.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: Update src/views/components/NewsletterList.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
pull/208/head v4.20.0
Aldino Kemal 1 year ago
committed by GitHub
parent
commit
c419406a9a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 301
      docs/openapi.yaml
  2. 7
      readme.md
  3. 2
      src/cmd/root.go
  4. 11
      src/domains/newsletter/newsletter.go
  5. 4
      src/domains/user/account.go
  6. 1
      src/domains/user/user.go
  7. 32
      src/internal/rest/newsletter.go
  8. 13
      src/internal/rest/user.go
  9. 8
      src/pkg/whatsapp/whatsapp.go
  10. 32
      src/services/newsletter.go
  11. 14
      src/services/user.go
  12. 20
      src/validations/newsletter_validation.go
  13. 25
      src/views/components/AccountAvatar.js
  14. 24
      src/views/components/AccountUserInfo.js
  15. 26
      src/views/components/MessageDelete.js
  16. 26
      src/views/components/MessageReact.js
  17. 26
      src/views/components/MessageRevoke.js
  18. 26
      src/views/components/MessageUpdate.js
  19. 117
      src/views/components/NewsletterList.js
  20. 25
      src/views/components/SendAudio.js
  21. 26
      src/views/components/SendContact.js
  22. 26
      src/views/components/SendFile.js
  23. 26
      src/views/components/SendImage.js
  24. 26
      src/views/components/SendLocation.js
  25. 25
      src/views/components/SendMessage.js
  26. 26
      src/views/components/SendPoll.js
  27. 26
      src/views/components/SendVideo.js
  28. 52
      src/views/components/generic/FormRecipient.js
  29. 17
      src/views/index.html

301
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:
@ -1342,3 +1403,241 @@ components:
type: object
example: null
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

7
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](https://i.ibb.co.com/L0B1LVb/homepage-v4-16.png) |
| Homepage | ![Homepage](https://i.ibb.co.com/Sy0dHZp/homepage-v4-20.png) |
| Login | ![Login](https://i.ibb.co.com/jkcB15R/login.png?v=1) |
| Login With Code | ![Login With Code](https://i.ibb.co.com/rdJGvGw/paircode.png) |
| Send Message | ![Send Message](https://i.ibb.co.com/rc3NXMX/send-message.png?v1) |
@ -167,6 +169,7 @@ You can fork or edit this source code !
| Auto Reply | ![Auto Reply](https://i.ibb.co.com/D4rTytX/IMG-20220517-162500.jpg) |
| Basic Auth Prompt | ![Basic Auth Prompt](https://i.ibb.co.com/PDjQ92W/Screenshot-2022-11-06-at-14-06-29.png) |
| Manage Participant | ![Manage Participant](https://i.ibb.co.com/ynrN7cr/manage-participant.png) |
| My Newsletter | ![List Newsletter](https://i.ibb.co.com/WDg50jJ/image.png) |
### Mac OS NOTE

2
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{

11
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"`
}

4
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"`
}

1
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)
}

32
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",
})
}

13
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,
})
}

8
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 == "" {
}
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 {

32
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)
}

14
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)

20
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
}

25
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 {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Preview</label>

24
src/views/components/AccountUserInfo.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'AccountUserInfo',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
//
name: null,
@ -15,7 +20,7 @@ export default {
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -52,7 +57,7 @@ export default {
this.name = null;
this.status = null;
this.devices = [];
this.type = 'user';
this.type = window.TYPEUSER;
}
},
template: `
@ -74,18 +79,7 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<button type="button" class="ui primary button" :class="{'loading': loading}"
@click="handleSubmit">

26
src/views/components/MessageDelete.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'DeleteMessage',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
message_id: '',
loading: false,
@ -10,7 +15,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -48,7 +53,7 @@ export default {
}
},
handleReset() {
this.type = 'user';
this.type = window.TYPEUSER;
this.phone = '';
this.message_id = '';
this.new_message = '';
@ -74,19 +79,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageReact.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'ReactMessage',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
message_id: '',
emoji: '',
@ -11,7 +16,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -51,7 +56,7 @@ export default {
this.phone = '';
this.message_id = '';
this.emoji = '';
this.type = 'user';
this.type = window.TYPEUSER;
},
},
template: `
@ -74,19 +79,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageRevoke.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'Message',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
message_id: '',
loading: false,
@ -10,7 +15,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -49,7 +54,7 @@ export default {
handleReset() {
this.phone = '';
this.message_id = '';
this.type = 'user';
this.type = window.TYPEUSER;
},
},
template: `
@ -71,19 +76,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label> Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageUpdate.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'UpdateMessage',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
message_id: '',
new_message: '',
@ -11,7 +16,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -49,7 +54,7 @@ export default {
}
},
handleReset() {
this.type = 'user';
this.type = window.TYPEUSER;
this.phone = '';
this.message_id = '';
this.new_message = '';
@ -75,19 +80,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id"

117
src/views/components/NewsletterList.js

@ -0,0 +1,117 @@
export default {
name: 'ListNewsletter',
data() {
return {
newsletters: []
}
},
methods: {
async openModal() {
try {
this.dtClear()
await this.submitApi();
$('#modalNewsletterList').modal('show');
this.dtRebuild()
showSuccessInfo("Newsletters fetched")
} catch (err) {
showErrorInfo(err)
}
},
dtClear() {
$('#account_newsletters_table').DataTable().destroy();
},
dtRebuild() {
$('#account_newsletters_table').DataTable({
"pageLength": 100,
"reloadData": true,
}).draw();
},
async handleUnfollowNewsletter(newsletter_id) {
try {
const ok = confirm("Are you sure to leave this newsletter?");
if (!ok) return;
await this.unfollowNewsletterApi(newsletter_id);
this.dtClear()
await this.submitApi();
this.dtRebuild()
showSuccessInfo("Success unfollow newsletter")
} catch (err) {
showErrorInfo(err)
}
},
async unfollowNewsletterApi(newsletter_id) {
try {
let payload = {
newsletter_id: newsletter_id
};
await window.http.post(`/newsletter/unfollow`, payload)
} catch (error) {
if (error.response) {
throw new Error(error.response.data.message);
}
throw new Error(error.message);
}
},
async submitApi() {
try {
let response = await window.http.get(`/user/my/newsletters`)
this.newsletters = response.data.results.data;
} catch (error) {
if (error.response) {
throw new Error(error.response.data.message);
}
throw new Error(error.message);
}
},
formatDate: function (value) {
if (!value) return ''
if (isNaN(value)) return 'Invalid date';
return moment.unix(value).format('LLL');
}
},
template: `
<div class="green card" @click="openModal" style="cursor: pointer">
<div class="content">
<a class="ui green right ribbon label">Newsletter</a>
<div class="header">List Newsletters</div>
<div class="description">
Display all your newsletters
</div>
</div>
</div>
<!-- Modal AccountNewsletter -->
<div class="ui small modal" id="modalNewsletterList">
<i class="close icon"></i>
<div class="header">
My Newsletter List
</div>
<div class="content">
<table class="ui celled table" id="account_newsletters_table">
<thead>
<tr>
<th>Newsletter ID</th>
<th>Name</th>
<th>Role</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody v-if="newsletters != null">
<tr v-for="n in newsletters">
<td>{{ n.id.split('@')[0] }}</td>
<td>{{ n.thread_metadata?.name?.text || 'N/A' }}</td>
<td>{{ n.viewer_metadata?.role || 'N/A' }}</td>
<td>{{ formatDate(n.thread_metadata?.creation_time) }}</td>
<td>
<button class="ui red tiny button" @click="handleUnfollowNewsletter(n.id)">Unfollow</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`
}

25
src/views/components/SendAudio.js

@ -1,15 +1,20 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'Send',
components: {
FormRecipient
},
data() {
return {
phone: '',
type: 'user',
type: window.TYPEUSER,
loading: false,
}
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -49,7 +54,7 @@ export default {
},
handleReset() {
this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_audio").val('');
},
},
@ -72,19 +77,7 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field" style="padding-bottom: 30px">
<label>Audio</label>
<input type="file" style="display: none" accept="audio/*" id="file_audio"/>

26
src/views/components/SendContact.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendContact',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
card_name: '',
card_phone: '',
@ -11,7 +16,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -58,7 +63,7 @@ export default {
this.phone = '';
this.card_name = '';
this.card_phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
},
},
template: `
@ -80,19 +85,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Contact Name</label>
<input v-model="card_name" type="text" placeholder="Please enter contact name"

26
src/views/components/SendFile.js

@ -1,5 +1,10 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendFile',
components: {
FormRecipient
},
props: {
maxFileSize: {
type: String,
@ -9,14 +14,14 @@ export default {
data() {
return {
caption: '',
type: 'user',
type: window.TYPEUSER,
phone: '',
loading: false,
}
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -58,7 +63,7 @@ export default {
handleReset() {
this.caption = '';
this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_file").val('');
},
},
@ -82,19 +87,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Caption</label>
<textarea v-model="caption" placeholder="Type some caption (optional)..."

26
src/views/components/SendImage.js

@ -1,19 +1,24 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendImage',
components: {
FormRecipient
},
data() {
return {
phone: '',
view_once: false,
compress: false,
caption: '',
type: 'user',
type: window.TYPEUSER,
loading: false,
selected_file: null
}
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -60,7 +65,7 @@ export default {
this.compress = false;
this.phone = '';
this.caption = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_image").val('');
},
},
@ -85,19 +90,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Caption</label>
<textarea v-model="caption" type="text" placeholder="Hello this is image caption"

26
src/views/components/SendLocation.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendLocation',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
latitude: '',
longitude: '',
@ -11,7 +16,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -56,7 +61,7 @@ export default {
this.phone = '';
this.latitude = '';
this.longitude = '';
this.type = 'user';
this.type = window.TYPEUSER;
},
},
template: `
@ -78,19 +83,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Location Latitude</label>
<input v-model="latitude" type="text" placeholder="Please enter latitude"

25
src/views/components/SendMessage.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendMessage',
components: {
FormRecipient
},
data() {
return {
type: 'user',
type: window.TYPEUSER,
phone: '',
text: '',
reply_message_id: '',
@ -11,7 +16,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -57,7 +62,7 @@ export default {
handleReset() {
this.phone = '';
this.text = '';
this.type = 'user';
this.type = window.TYPEUSER;
this.reply_message_id = '';
},
},
@ -80,19 +85,7 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Reply Message ID</label>
<input v-model="reply_message_id" type="text"

26
src/views/components/SendPoll.js

@ -1,10 +1,15 @@
// export Vue Component
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendPoll',
components: {
FormRecipient
},
data() {
return {
phone: '',
type: 'user',
type: window.TYPEUSER,
loading: false,
question: '',
options: ['', ''],
@ -13,7 +18,7 @@ export default {
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -56,7 +61,7 @@ export default {
},
handleReset() {
this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
this.question = '';
this.options = ['', ''];
this.max_vote = 1;
@ -87,19 +92,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Question</label>
<input v-model="question" type="text" placeholder="Please enter question"

26
src/views/components/SendVideo.js

@ -1,5 +1,10 @@
import FormRecipient from "./generic/FormRecipient.js";
export default {
name: 'SendVideo',
components: {
FormRecipient
},
// define props
props: {
maxVideoSize: {
@ -12,14 +17,14 @@ export default {
caption: '',
view_once: false,
compress: false,
type: 'user',
type: window.TYPEUSER,
phone: '',
loading: false,
}
},
computed: {
phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
}
},
methods: {
@ -65,7 +70,7 @@ export default {
this.view_once = false;
this.compress = false;
this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_video").val('');
},
},
@ -91,19 +96,8 @@ export default {
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field">
<label>Caption</label>
<textarea v-model="caption" placeholder="Type some caption (optional)..."

52
src/views/components/generic/FormRecipient.js

@ -0,0 +1,52 @@
export default {
name: 'FormRecipient',
props: {
type: {
type: String,
required: true
},
phone: {
type: String,
required: true
},
},
data() {
return {
recipientTypes: []
};
},
computed: {
phone_id() {
return this.phone + this.type;
}
},
mounted() {
this.recipientTypes = [
{ value: window.TYPEUSER, text: 'Private Message' },
{ value: window.TYPEGROUP, text: 'Group Message' },
{ value: window.TYPENEWSLETTER, text: 'Newsletter' }
];
},
methods: {
updateType(event) {
this.$emit('update:type', event.target.value);
},
updatePhone(event) {
this.$emit('update:phone', event.target.value);
}
},
template: `
<div class="field">
<label>Type</label>
<select name="type" @change="updateType" class="ui dropdown">
<option v-for="type in recipientTypes" :value="type.value">{{ type.text }}</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input :value="phone" aria-label="wa identifier" @input="updatePhone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
`
}

17
src/views/index.html

@ -86,6 +86,14 @@
<group-add-participants></group-add-participants>
</div>
<div class="ui horizontal divider">
Newsletter
</div>
<div class="ui three column doubling grid cards">
<newsletter-list></newsletter-list>
</div>
<div class="ui horizontal divider">
Account
</div>
@ -98,8 +106,9 @@
</div>
<script>
window.TYPEGROUP = "g.us";
window.TYPEUSER = "s.whatsapp.net";
window.TYPEGROUP = "@g.us";
window.TYPEUSER = "@s.whatsapp.net";
window.TYPENEWSLETTER = "@newsletter";
window.showErrorInfo = (message) => {
$('body').toast({
position: 'bottom right',
@ -120,7 +129,7 @@
}
window.http = axios.create({
baseURL: `${window.location.protocol}//${window.location.hostname}`
baseURL: `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`
});
{{ if isEnableBasicAuth .BasicAuthToken }}
window.http.defaults.headers.common['Authorization'] = {{ .BasicAuthToken }};
@ -150,6 +159,7 @@
import AccountAvatar from "./components/AccountAvatar.js";
import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js";
import NewsletterList from "./components/NewsletterList.js";
const showErrorInfo = (message) => {
$('body').toast({
@ -176,6 +186,7 @@
SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll,
MessageDelete, MessageUpdate, MessageReact, MessageRevoke,
GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants,
NewsletterList,
AccountAvatar, AccountUserInfo, AccountPrivacy
},
delimiters: ['[[', ']]'],

Loading…
Cancel
Save