Browse Source

feat: add send contact (#18)

* feat: add send contact backend service

* feat: add UI for send contact

* chore: update docs

* chore: update openapi & upgrade app version
pull/21/head v3.4.0
Aldino Kemal 4 years ago
committed by GitHub
parent
commit
3df6f896fc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 96
      docs/openapi.yaml
  2. 5
      readme.md
  3. 2
      src/config/settings.go
  4. 25
      src/controllers/send_controller.go
  5. 1
      src/services/send_service.go
  6. 22
      src/services/send_service_impl.go
  7. 11
      src/structs/send_struct.go
  8. 14
      src/validations/send_validation.go
  9. 197
      src/views/index.html

96
docs/openapi.yaml

@ -791,4 +791,100 @@ paths:
code: 500 code: 500
message: you are not loggin message: you are not loggin
results: null results: null
/send/contact:
post:
tags:
- send
summary: Send Contact
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
phone:
type: integer
example: '6289685024051'
contact_name:
type: string
example: Aldino Kemal
contact_phone:
type: string
example: '6289685024992'
type:
type: string
example: 'user'
description: 'user/group | default: user'
responses:
'200':
description: OK
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:42:57 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '123'
content:
application/json:
schema:
type: object
example:
code: 200
message: Success
results:
status: >-
Contact sent to 6289685024051@s.whatsapp.net (server timestamp: 2022-05-17 14:39:29 +0700 WIB)
'400':
description: Bad Request
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:02:17 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '70'
content:
application/json:
schema:
type: object
example:
code: 400
message: 'phone: cannot be blank.'
results: null
'500':
description: Internal Server Error
headers:
Date:
schema:
type: string
example: Fri, 11 Feb 2022 03:02:48 GMT
Content-Type:
schema:
type: string
example: application/json
Content-Length:
schema:
type: integer
example: '58'
content:
application/json:
schema:
type: object
example:
code: 500
message: you are not loggin
results: null

5
readme.md

@ -82,7 +82,7 @@ You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API
| ✅ | Send Image | POST | /send/image | | ✅ | Send Image | POST | /send/image |
| ✅ | Send File | POST | /send/file | | ✅ | Send File | POST | /send/file |
| ✅ | Send Video | POST | /send/video | | ✅ | Send Video | POST | /send/video |
| | Send Contact | POST | /send/contact |
| | Send Contact | POST | /send/contact |
``` ```
✅ = Available ✅ = Available
@ -91,12 +91,13 @@ You can check [docs/openapi.yml](./docs/openapi.yaml) for detail API
### App User Interface ### App User Interface
1. Homepage ![Homepage](https://i.ibb.co/nCK9w1W/Screen-Shot-2022-05-22-at-13-39-28.png)
1. Homepage ![Homepage](https://i.ibb.co/ScYLjw8/Screen-Shot-2022-07-07-at-07-57-46.png)
2. Login ![Login](https://i.ibb.co/Yp3YJKM/Screen-Shot-2022-02-13-at-12-55-54.png) 2. Login ![Login](https://i.ibb.co/Yp3YJKM/Screen-Shot-2022-02-13-at-12-55-54.png)
3. Send Message ![Send Message](https://i.ibb.co/Bng57Ry/Screen-Shot-2022-05-22-at-13-51-13.png) 3. Send Message ![Send Message](https://i.ibb.co/Bng57Ry/Screen-Shot-2022-05-22-at-13-51-13.png)
4. Send Image ![Send Image](https://i.ibb.co/gJj3SrQ/Screen-Shot-2022-05-22-at-13-49-21.png) 4. Send Image ![Send Image](https://i.ibb.co/gJj3SrQ/Screen-Shot-2022-05-22-at-13-49-21.png)
5. Send File ![Send File](https://i.ibb.co/nCwhysd/Screen-Shot-2022-05-22-at-13-43-16.png) 5. Send File ![Send File](https://i.ibb.co/nCwhysd/Screen-Shot-2022-05-22-at-13-43-16.png)
6. Send Video ![Send File](https://i.ibb.co/yBXsWXX/Screen-Shot-2022-05-22-at-13-43-24.png) 6. Send Video ![Send File](https://i.ibb.co/yBXsWXX/Screen-Shot-2022-05-22-at-13-43-24.png)
6. Send Contact ![Send File](https://i.ibb.co/fqwwGK5/Screen-Shot-2022-07-07-at-07-59-26.png)
7. User Info ![User Info](https://i.ibb.co/BC0mNT7/Screen-Shot-2022-02-13-at-13-00-57.png) 7. User Info ![User Info](https://i.ibb.co/BC0mNT7/Screen-Shot-2022-02-13-at-13-00-57.png)
8. User Avatar ![User Avatar](https://i.ibb.co/TkzPbLZ/Screen-Shot-2022-02-13-at-13-01-39.png) 8. User Avatar ![User Avatar](https://i.ibb.co/TkzPbLZ/Screen-Shot-2022-02-13-at-13-01-39.png)
9. User Privacy ![User My Privacy](https://i.ibb.co/RQcC5m9/Screen-Shot-2022-02-13-at-12-58-47.png) 9. User Privacy ![User My Privacy](https://i.ibb.co/RQcC5m9/Screen-Shot-2022-02-13-at-12-58-47.png)

2
src/config/settings.go

@ -3,7 +3,7 @@ package config
type Browser string type Browser string
var ( var (
AppVersion string = "3.3.1"
AppVersion string = "3.4.0"
AppPort string = "3000" AppPort string = "3000"
AppDebug bool = false AppDebug bool = false

25
src/controllers/send_controller.go

@ -21,6 +21,7 @@ func (controller *SendController) Route(app *fiber.App) {
app.Post("/send/image", controller.SendImage) app.Post("/send/image", controller.SendImage)
app.Post("/send/file", controller.SendFile) app.Post("/send/file", controller.SendFile)
app.Post("/send/video", controller.SendVideo) app.Post("/send/video", controller.SendVideo)
app.Post("/send/contact", controller.SendContact)
} }
func (controller *SendController) SendText(c *fiber.Ctx) error { func (controller *SendController) SendText(c *fiber.Ctx) error {
@ -135,3 +136,27 @@ func (controller *SendController) SendVideo(c *fiber.Ctx) error {
Results: response, Results: response,
}) })
} }
func (controller SendController) SendContact(c *fiber.Ctx) error {
var request structs.SendContactRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
// add validation send contect
validations.ValidateSendContact(request)
if request.Type == structs.TypeGroup {
request.Phone = request.Phone + "@g.us"
} else {
request.Phone = request.Phone + "@s.whatsapp.net"
}
response, err := controller.Service.SendContact(c, request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Code: 200,
Message: response.Status,
Results: response,
})
}

1
src/services/send_service.go

@ -10,4 +10,5 @@ type SendService interface {
SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error) SendImage(c *fiber.Ctx, request structs.SendImageRequest) (response structs.SendImageResponse, err error)
SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error) SendFile(c *fiber.Ctx, request structs.SendFileRequest) (response structs.SendFileResponse, err error)
SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error) SendVideo(c *fiber.Ctx, request structs.SendVideoRequest) (response structs.SendVideoResponse, err error)
SendContact(c *fiber.Ctx, request structs.SendContactRequest) (response structs.SendContactResponse, err error)
} }

22
src/services/send_service_impl.go

@ -286,3 +286,25 @@ func (service SendServiceImpl) SendVideo(c *fiber.Ctx, request structs.SendVideo
return response, nil return response, nil
} }
} }
func (service SendServiceImpl) SendContact(_ *fiber.Ctx, request structs.SendContactRequest) (response structs.SendContactResponse, err error) {
utils.MustLogin(service.WaCli)
recipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD",
request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone)
msg := &waProto.Message{ContactMessage: &waProto.ContactMessage{
DisplayName: proto.String(request.ContactName),
Vcard: proto.String(msgVCard),
}}
ts, err := service.WaCli.SendMessage(recipient, "", msg)
if err != nil {
return response, err
} else {
response.Status = fmt.Sprintf("Contact sent to %s (server timestamp: %s)", request.Phone, ts)
}
return response, nil
}

11
src/structs/send_struct.go

@ -54,3 +54,14 @@ type SendVideoRequest struct {
type SendVideoResponse struct { type SendVideoResponse struct {
Status string `json:"status"` Status string `json:"status"`
} }
type SendContactRequest struct {
Phone string `json:"phone" form:"phone"`
ContactName string `json:"contact_name" form:"contact_name"`
ContactPhone string `json:"contact_phone" form:"contact_phone"`
Type SendType `json:"type" form:"type"`
}
type SendContactResponse struct {
Status string `json:"status"`
}

14
src/validations/send_validation.go

@ -99,3 +99,17 @@ func ValidateSendVideo(request structs.SendVideoRequest) {
}) })
} }
} }
func ValidateSendContact(request structs.SendContactRequest) {
err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required, validation.Length(10, 25)),
validation.Field(&request.ContactName, validation.Required),
validation.Field(&request.ContactPhone, validation.Required),
)
if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
}
}

197
src/views/index.html

@ -27,6 +27,11 @@
<h1 class="ui dividing header">[[ app_name ]]</h1> <h1 class="ui dividing header">[[ app_name ]]</h1>
<h3 class="first">Features</h3> <h3 class="first">Features</h3>
<div class="ui horizontal divider">
App
</div>
<div class="ui three column stackable grid cards"> <div class="ui three column stackable grid cards">
<div class="green card" @click="loginModal" style="cursor: pointer"> <div class="green card" @click="loginModal" style="cursor: pointer">
<div class="content"> <div class="content">
@ -55,42 +60,11 @@
</div> </div>
</div> </div>
<div class="ui four column doubling grid cards">
<div class="green card" @click="infoModal" style="cursor: pointer;">
<div class="content">
<div class="header">User Info</div>
<div class="description">
You can search someone user info by phone
</div>
</div>
</div>
<div class="green card" @click="avatarModal" style="cursor: pointer;">
<div class="content">
<div class="header">User Avatar</div>
<div class="description">
You can search someone avatar by phone
</div>
</div>
</div>
<div class="green card" @click="groupModal" style="cursor: pointer">
<div class="content">
<div class="header">User List Groups</div>
<div class="description">
Display all groups you joined
</div>
</div>
</div>
<div class="green card" @click="privacyModal" style="cursor: pointer">
<div class="content">
<div class="header">User Privacy Setting</div>
<div class="description">
Get your privacy settings
</div>
</div>
</div>
<div class="ui horizontal divider">
Send Private Message
</div> </div>
<div class="ui four column doubling grid cards">
<div class="ui three column doubling grid cards">
<div class="green card" @click="sendMessageModal('user')" style="cursor: pointer"> <div class="green card" @click="sendMessageModal('user')" style="cursor: pointer">
<div class="content"> <div class="content">
<a class="ui blue right ribbon label">Private</a> <a class="ui blue right ribbon label">Private</a>
@ -133,9 +107,22 @@
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendContactModal('user')" style="cursor: pointer">
<div class="content">
<a class="ui blue right ribbon label">Contact</a>
<div class="header">Send Contact</div>
<div class="description">
Send contact to any whatsapp number
</div>
</div>
</div>
</div> </div>
<div class="ui four column doubling grid cards">
<div class="ui horizontal divider">
Send Group Message
</div>
<div class="ui three column doubling grid cards">
<div class="green card" @click="sendMessageModal('group')" style="cursor: pointer"> <div class="green card" @click="sendMessageModal('group')" style="cursor: pointer">
<div class="content"> <div class="content">
<a class="ui teal right ribbon label">Group</a> <a class="ui teal right ribbon label">Group</a>
@ -178,6 +165,54 @@
</div> </div>
</div> </div>
</div> </div>
<div class="green card" @click="sendContactModal('group')" style="cursor: pointer">
<div class="content">
<a class="ui teal right ribbon label">Contact</a>
<div class="header">Send Contact</div>
<div class="description">
Send contact to any whatsapp number
</div>
</div>
</div>
</div>
<div class="ui horizontal divider">
User
</div>
<div class="ui four column doubling grid cards">
<div class="green card" @click="infoModal" style="cursor: pointer;">
<div class="content">
<div class="header">User Info</div>
<div class="description">
You can search someone user info by phone
</div>
</div>
</div>
<div class="green card" @click="avatarModal" style="cursor: pointer;">
<div class="content">
<div class="header">User Avatar</div>
<div class="description">
You can search someone avatar by phone
</div>
</div>
</div>
<div class="green card" @click="groupModal" style="cursor: pointer">
<div class="content">
<div class="header">User List Groups</div>
<div class="description">
Display all groups you joined
</div>
</div>
</div>
<div class="green card" @click="privacyModal" style="cursor: pointer">
<div class="content">
<div class="header">User Privacy Setting</div>
<div class="description">
Get your privacy settings
</div>
</div>
</div>
</div> </div>
@ -369,6 +404,39 @@
</div> </div>
</div> </div>
<!-- Modal SendContact -->
<div class="ui small modal" id="modalSendContact">
<i class="close icon"></i>
<div class="header">
Send Contact
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Phone / Group ID</label>
<input v-model="contact_phone" type="text" placeholder="6289..."
aria-label="phone">
</div>
<div class="field">
<label>Contact Name</label>
<input v-model="contact_card_name" type="text" placeholder="Please enter contact name"
aria-label="card_name">
</div>
<div class="field">
<label>Contact Phone</label>
<input v-model="contact_card_phone" type="text" placeholder="Please enter contact phone"
aria-label="card_phone">
</div>
</form>
</div>
<div class="actions">
<div class="ui positive right labeled icon button" @click="sendContactProcess">
Send
<i class="send icon"></i>
</div>
</div>
</div>
<!-- Modal UserGroup --> <!-- Modal UserGroup -->
<div class="ui small modal" id="modalUserGroup"> <div class="ui small modal" id="modalUserGroup">
<i class="close icon"></i> <i class="close icon"></i>
@ -823,6 +891,63 @@
} }
} }
const sendContact = {
data() {
return {
contact_type: 'user',
contact_phone: '',
contact_card_name: '',
contact_card_phone: '',
}
},
methods: {
sendContactModal(type) {
this.contact_type = type
$('#modalSendContact').modal('show');
},
async sendContactProcess() {
try {
let response = await this.sendContactApi()
showSuccessInfo(response)
} catch (err) {
showErrorInfo(err)
}
},
sendContactApi() {
return new Promise(async (resolve, reject) => {
try {
let payload = new FormData();
payload.append("phone", this.contact_phone)
payload.append("contact_name", this.contact_card_name)
payload.append("contact_phone", this.contact_card_phone)
payload.append("type", this.contact_type)
let response = await axios.post(`${this.app_host}/send/contact`, payload, {
// Axios Bug, always content-type that make boundary not set default by browser https://github.com/axios/axios/issues/1603
transformRequest: (data, headers) => {
delete headers.post['Content-Type'];
return data;
}
})
this.sendContactReset();
resolve(response.data.message)
} catch (error) {
if (error.response) {
reject(error.response.data.message)
} else {
reject(error.message)
}
}
})
},
sendContactReset() {
this.contact_phone = '';
this.contact_card_name = '';
this.contact_card_phone = '';
this.message_type = 'user';
},
}
}
const userGroups = { const userGroups = {
data() { data() {
return { return {
@ -1003,7 +1128,7 @@
app_name: 'Whatsapp API Multi Device (v{{ .AppVersion }})' app_name: 'Whatsapp API Multi Device (v{{ .AppVersion }})'
} }
}, },
mixins: [login, logout, reconnect, sendMessage, sendImage, sendFile, sendVideo, userGroups, userPrivacy, userAvatar, userInfo]
mixins: [login, logout, reconnect, sendMessage, sendImage, sendFile, sendVideo, sendContact, userGroups, userPrivacy, userAvatar, userInfo]
}).mount('#app') }).mount('#app')
</script> </script>
</body> </body>
Loading…
Cancel
Save