|
|
|
package whatsapp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math/rand"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Rhymen/go-whatsapp/binary"
|
|
|
|
"github.com/Rhymen/go-whatsapp/binary/proto"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MediaType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
MediaImage MediaType = "WhatsApp Image Keys"
|
|
|
|
MediaVideo MediaType = "WhatsApp Video Keys"
|
|
|
|
MediaAudio MediaType = "WhatsApp Audio Keys"
|
|
|
|
MediaDocument MediaType = "WhatsApp Document Keys"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (wac *Conn) Send(msg interface{}) (string, error) {
|
|
|
|
var msgProto *proto.WebMessageInfo
|
|
|
|
|
|
|
|
switch m := msg.(type) {
|
|
|
|
case *proto.WebMessageInfo:
|
|
|
|
msgProto = m
|
|
|
|
case TextMessage:
|
|
|
|
msgProto = getTextProto(m)
|
|
|
|
case ImageMessage:
|
|
|
|
var err error
|
|
|
|
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaImage)
|
|
|
|
if err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("image upload failed: %v", err)
|
|
|
|
}
|
|
|
|
msgProto = getImageProto(m)
|
|
|
|
case VideoMessage:
|
|
|
|
var err error
|
|
|
|
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaVideo)
|
|
|
|
if err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("video upload failed: %v", err)
|
|
|
|
}
|
|
|
|
msgProto = getVideoProto(m)
|
|
|
|
case DocumentMessage:
|
|
|
|
var err error
|
|
|
|
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaDocument)
|
|
|
|
if err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("document upload failed: %v", err)
|
|
|
|
}
|
|
|
|
msgProto = getDocumentProto(m)
|
|
|
|
case AudioMessage:
|
|
|
|
var err error
|
|
|
|
m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaAudio)
|
|
|
|
if err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("audio upload failed: %v", err)
|
|
|
|
}
|
|
|
|
msgProto = getAudioProto(m)
|
|
|
|
case LocationMessage:
|
|
|
|
msgProto = GetLocationProto(m)
|
|
|
|
case LiveLocationMessage:
|
|
|
|
msgProto = GetLiveLocationProto(m)
|
|
|
|
case ContactMessage:
|
|
|
|
msgProto = getContactMessageProto(m)
|
|
|
|
default:
|
|
|
|
return "ERROR", fmt.Errorf("cannot match type %T, use message types declared in the package", msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
ch, err := wac.sendProto(msgProto)
|
|
|
|
if err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("could not send proto: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case response := <-ch:
|
|
|
|
var resp map[string]interface{}
|
|
|
|
if err = json.Unmarshal([]byte(response), &resp); err != nil {
|
|
|
|
return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err)
|
|
|
|
}
|
|
|
|
if int(resp["status"].(float64)) != 200 {
|
|
|
|
return "ERROR", fmt.Errorf("message sending responded with %v", resp["status"])
|
|
|
|
}
|
|
|
|
if int(resp["status"].(float64)) == 200 {
|
|
|
|
return getMessageInfo(msgProto).Id, nil
|
|
|
|
}
|
|
|
|
case <-time.After(wac.msgTimeout):
|
|
|
|
return "ERROR", fmt.Errorf("sending message timed out")
|
|
|
|
}
|
|
|
|
|
|
|
|
return "ERROR", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) {
|
|
|
|
n := binary.Node{
|
|
|
|
Description: "action",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"type": "relay",
|
|
|
|
"epoch": strconv.Itoa(wac.msgCount),
|
|
|
|
},
|
|
|
|
Content: []interface{}{p},
|
|
|
|
}
|
|
|
|
return wac.writeBinary(n, message, ignore, p.Key.GetId())
|
|
|
|
}
|
|
|
|
|
|
|
|
// RevokeMessage revokes a message (marks as "message removed") for everyone
|
|
|
|
func (wac *Conn) RevokeMessage(remotejid, msgid string, fromme bool) (revokeid string, err error) {
|
|
|
|
// create a revocation ID (required)
|
|
|
|
rawrevocationID := make([]byte, 10)
|
|
|
|
rand.Read(rawrevocationID)
|
|
|
|
revocationID := strings.ToUpper(hex.EncodeToString(rawrevocationID))
|
|
|
|
//
|
|
|
|
ts := uint64(time.Now().Unix())
|
|
|
|
status := proto.WebMessageInfo_PENDING
|
|
|
|
mtype := proto.ProtocolMessage_REVOKE
|
|
|
|
|
|
|
|
revoker := &proto.WebMessageInfo{
|
|
|
|
Key: &proto.MessageKey{
|
|
|
|
FromMe: &fromme,
|
|
|
|
Id: &revocationID,
|
|
|
|
RemoteJid: &remotejid,
|
|
|
|
},
|
|
|
|
MessageTimestamp: &ts,
|
|
|
|
Message: &proto.Message{
|
|
|
|
ProtocolMessage: &proto.ProtocolMessage{
|
|
|
|
Type: &mtype,
|
|
|
|
Key: &proto.MessageKey{
|
|
|
|
FromMe: &fromme,
|
|
|
|
Id: &msgid,
|
|
|
|
RemoteJid: &remotejid,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Status: &status,
|
|
|
|
}
|
|
|
|
if _, err := wac.Send(revoker); err != nil {
|
|
|
|
return revocationID, err
|
|
|
|
}
|
|
|
|
return revocationID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteMessage deletes a single message for the user (removes the msgbox). To
|
|
|
|
// delete the message for everyone, use RevokeMessage
|
|
|
|
func (wac *Conn) DeleteMessage(remotejid, msgid string, fromMe bool) error {
|
|
|
|
ch, err := wac.deleteChatProto(remotejid, msgid, fromMe)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not send proto: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case response := <-ch:
|
|
|
|
var resp map[string]interface{}
|
|
|
|
if err = json.Unmarshal([]byte(response), &resp); err != nil {
|
|
|
|
return fmt.Errorf("error decoding deletion response: %v", err)
|
|
|
|
}
|
|
|
|
if int(resp["status"].(float64)) != 200 {
|
|
|
|
return fmt.Errorf("message deletion responded with %v", resp["status"])
|
|
|
|
}
|
|
|
|
if int(resp["status"].(float64)) == 200 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case <-time.After(wac.msgTimeout):
|
|
|
|
return fmt.Errorf("deleting message timed out")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wac *Conn) deleteChatProto(remotejid, msgid string, fromMe bool) (<-chan string, error) {
|
|
|
|
tag := fmt.Sprintf("%s.--%d", wac.timeTag, wac.msgCount)
|
|
|
|
|
|
|
|
owner := "true"
|
|
|
|
if !fromMe {
|
|
|
|
owner = "false"
|
|
|
|
}
|
|
|
|
n := binary.Node{
|
|
|
|
Description: "action",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"epoch": strconv.Itoa(wac.msgCount),
|
|
|
|
"type": "set",
|
|
|
|
},
|
|
|
|
Content: []interface{}{
|
|
|
|
binary.Node{
|
|
|
|
Description: "chat",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"type": "clear",
|
|
|
|
"jid": remotejid,
|
|
|
|
"media": "true",
|
|
|
|
},
|
|
|
|
Content: []binary.Node{
|
|
|
|
{
|
|
|
|
Description: "item",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"owner": owner,
|
|
|
|
"index": msgid,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return wac.writeBinary(n, chat, expires|skipOffline, tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
MessageInfo contains general message information. It is part of every of every message type.
|
|
|
|
*/
|
|
|
|
type MessageInfo struct {
|
|
|
|
Id string
|
|
|
|
RemoteJid string
|
|
|
|
SenderJid string
|
|
|
|
FromMe bool
|
|
|
|
Timestamp uint64
|
|
|
|
PushName string
|
|
|
|
Status MessageStatus
|
|
|
|
|
|
|
|
Source *proto.WebMessageInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
type MessageStatus int
|
|
|
|
|
|
|
|
const (
|
|
|
|
Error MessageStatus = 0
|
|
|
|
Pending = 1
|
|
|
|
ServerAck = 2
|
|
|
|
DeliveryAck = 3
|
|
|
|
Read = 4
|
|
|
|
Played = 5
|
|
|
|
)
|
|
|
|
|
|
|
|
func getMessageInfo(msg *proto.WebMessageInfo) MessageInfo {
|
|
|
|
return MessageInfo{
|
|
|
|
Id: msg.GetKey().GetId(),
|
|
|
|
RemoteJid: msg.GetKey().GetRemoteJid(),
|
|
|
|
SenderJid: msg.GetParticipant(),
|
|
|
|
FromMe: msg.GetKey().GetFromMe(),
|
|
|
|
Timestamp: msg.GetMessageTimestamp(),
|
|
|
|
Status: MessageStatus(msg.GetStatus()),
|
|
|
|
PushName: msg.GetPushName(),
|
|
|
|
Source: msg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getInfoProto(info *MessageInfo) *proto.WebMessageInfo {
|
|
|
|
if info.Id == "" || len(info.Id) < 2 {
|
|
|
|
b := make([]byte, 10)
|
|
|
|
rand.Read(b)
|
|
|
|
info.Id = strings.ToUpper(hex.EncodeToString(b))
|
|
|
|
}
|
|
|
|
if info.Timestamp == 0 {
|
|
|
|
info.Timestamp = uint64(time.Now().Unix())
|
|
|
|
}
|
|
|
|
info.FromMe = true
|
|
|
|
|
|
|
|
status := proto.WebMessageInfo_WEB_MESSAGE_INFO_STATUS(info.Status)
|
|
|
|
|
|
|
|
return &proto.WebMessageInfo{
|
|
|
|
Key: &proto.MessageKey{
|
|
|
|
FromMe: &info.FromMe,
|
|
|
|
RemoteJid: &info.RemoteJid,
|
|
|
|
Id: &info.Id,
|
|
|
|
},
|
|
|
|
MessageTimestamp: &info.Timestamp,
|
|
|
|
Status: &status,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
ContextInfo represents contextinfo of every message
|
|
|
|
*/
|
|
|
|
type ContextInfo struct {
|
|
|
|
QuotedMessageID string //StanzaId
|
|
|
|
QuotedMessage *proto.Message
|
|
|
|
Participant string
|
|
|
|
IsForwarded bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMessageContext(msg *proto.ContextInfo) ContextInfo {
|
|
|
|
|
|
|
|
return ContextInfo{
|
|
|
|
QuotedMessageID: msg.GetStanzaId(), //StanzaId
|
|
|
|
QuotedMessage: msg.GetQuotedMessage(),
|
|
|
|
Participant: msg.GetParticipant(),
|
|
|
|
IsForwarded: msg.GetIsForwarded(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getContextInfoProto(context *ContextInfo) *proto.ContextInfo {
|
|
|
|
if len(context.QuotedMessageID) > 0 {
|
|
|
|
contextInfo := &proto.ContextInfo{
|
|
|
|
StanzaId: &context.QuotedMessageID,
|
|
|
|
}
|
|
|
|
|
|
|
|
if &context.QuotedMessage != nil {
|
|
|
|
contextInfo.QuotedMessage = context.QuotedMessage
|
|
|
|
contextInfo.Participant = &context.Participant
|
|
|
|
}
|
|
|
|
|
|
|
|
return contextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
TextMessage represents a text message.
|
|
|
|
*/
|
|
|
|
type TextMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
Text string
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
|
|
|
|
text := TextMessage{Info: getMessageInfo(msg)}
|
|
|
|
if m := msg.GetMessage().GetExtendedTextMessage(); m != nil {
|
|
|
|
text.Text = m.GetText()
|
|
|
|
|
|
|
|
text.ContextInfo = getMessageContext(m.GetContextInfo())
|
|
|
|
} else {
|
|
|
|
text.Text = msg.GetMessage().GetConversation()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTextProto(msg TextMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
|
|
|
|
if contextInfo == nil {
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
Conversation: &msg.Text,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
ExtendedTextMessage: &proto.ExtendedTextMessage{
|
|
|
|
Text: &msg.Text,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
ImageMessage represents a image message. Unexported fields are needed for media up/downloading and media validation.
|
|
|
|
Provide a io.Reader as Content for message sending.
|
|
|
|
*/
|
|
|
|
type ImageMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
Caption string
|
|
|
|
Thumbnail []byte
|
|
|
|
Type string
|
|
|
|
Content io.Reader
|
|
|
|
url string
|
|
|
|
mediaKey []byte
|
|
|
|
fileEncSha256 []byte
|
|
|
|
fileSha256 []byte
|
|
|
|
fileLength uint64
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
|
|
|
|
image := msg.GetMessage().GetImageMessage()
|
|
|
|
|
|
|
|
imageMessage := ImageMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
Caption: image.GetCaption(),
|
|
|
|
Thumbnail: image.GetJpegThumbnail(),
|
|
|
|
url: image.GetUrl(),
|
|
|
|
mediaKey: image.GetMediaKey(),
|
|
|
|
Type: image.GetMimetype(),
|
|
|
|
fileEncSha256: image.GetFileEncSha256(),
|
|
|
|
fileSha256: image.GetFileSha256(),
|
|
|
|
fileLength: image.GetFileLength(),
|
|
|
|
ContextInfo: getMessageContext(image.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return imageMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getImageProto(msg ImageMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
ImageMessage: &proto.ImageMessage{
|
|
|
|
Caption: &msg.Caption,
|
|
|
|
JpegThumbnail: msg.Thumbnail,
|
|
|
|
Url: &msg.url,
|
|
|
|
MediaKey: msg.mediaKey,
|
|
|
|
Mimetype: &msg.Type,
|
|
|
|
FileEncSha256: msg.fileEncSha256,
|
|
|
|
FileSha256: msg.fileSha256,
|
|
|
|
FileLength: &msg.fileLength,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Download is the function to retrieve media data. The media gets downloaded, validated and returned.
|
|
|
|
*/
|
|
|
|
func (m *ImageMessage) Download() ([]byte, error) {
|
|
|
|
return Download(m.url, m.mediaKey, MediaImage, int(m.fileLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
VideoMessage represents a video message. Unexported fields are needed for media up/downloading and media validation.
|
|
|
|
Provide a io.Reader as Content for message sending.
|
|
|
|
*/
|
|
|
|
type VideoMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
Caption string
|
|
|
|
Thumbnail []byte
|
|
|
|
Length uint32
|
|
|
|
Type string
|
|
|
|
Content io.Reader
|
|
|
|
GifPlayback bool
|
|
|
|
url string
|
|
|
|
mediaKey []byte
|
|
|
|
fileEncSha256 []byte
|
|
|
|
fileSha256 []byte
|
|
|
|
fileLength uint64
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
|
|
|
|
vid := msg.GetMessage().GetVideoMessage()
|
|
|
|
|
|
|
|
videoMessage := VideoMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
Caption: vid.GetCaption(),
|
|
|
|
Thumbnail: vid.GetJpegThumbnail(),
|
|
|
|
GifPlayback: vid.GetGifPlayback(),
|
|
|
|
url: vid.GetUrl(),
|
|
|
|
mediaKey: vid.GetMediaKey(),
|
|
|
|
Length: vid.GetSeconds(),
|
|
|
|
Type: vid.GetMimetype(),
|
|
|
|
fileEncSha256: vid.GetFileEncSha256(),
|
|
|
|
fileSha256: vid.GetFileSha256(),
|
|
|
|
fileLength: vid.GetFileLength(),
|
|
|
|
ContextInfo: getMessageContext(vid.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return videoMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getVideoProto(msg VideoMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
VideoMessage: &proto.VideoMessage{
|
|
|
|
Caption: &msg.Caption,
|
|
|
|
JpegThumbnail: msg.Thumbnail,
|
|
|
|
Url: &msg.url,
|
|
|
|
GifPlayback: &msg.GifPlayback,
|
|
|
|
MediaKey: msg.mediaKey,
|
|
|
|
Seconds: &msg.Length,
|
|
|
|
FileEncSha256: msg.fileEncSha256,
|
|
|
|
FileSha256: msg.fileSha256,
|
|
|
|
FileLength: &msg.fileLength,
|
|
|
|
Mimetype: &msg.Type,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Download is the function to retrieve media data. The media gets downloaded, validated and returned.
|
|
|
|
*/
|
|
|
|
func (m *VideoMessage) Download() ([]byte, error) {
|
|
|
|
return Download(m.url, m.mediaKey, MediaVideo, int(m.fileLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
AudioMessage represents a audio message. Unexported fields are needed for media up/downloading and media validation.
|
|
|
|
Provide a io.Reader as Content for message sending.
|
|
|
|
*/
|
|
|
|
type AudioMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
Length uint32
|
|
|
|
Type string
|
|
|
|
Content io.Reader
|
|
|
|
Ptt bool
|
|
|
|
url string
|
|
|
|
mediaKey []byte
|
|
|
|
fileEncSha256 []byte
|
|
|
|
fileSha256 []byte
|
|
|
|
fileLength uint64
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
|
|
|
|
aud := msg.GetMessage().GetAudioMessage()
|
|
|
|
|
|
|
|
audioMessage := AudioMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
url: aud.GetUrl(),
|
|
|
|
mediaKey: aud.GetMediaKey(),
|
|
|
|
Length: aud.GetSeconds(),
|
|
|
|
Type: aud.GetMimetype(),
|
|
|
|
fileEncSha256: aud.GetFileEncSha256(),
|
|
|
|
fileSha256: aud.GetFileSha256(),
|
|
|
|
fileLength: aud.GetFileLength(),
|
|
|
|
ContextInfo: getMessageContext(aud.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return audioMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAudioProto(msg AudioMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
AudioMessage: &proto.AudioMessage{
|
|
|
|
Url: &msg.url,
|
|
|
|
MediaKey: msg.mediaKey,
|
|
|
|
Seconds: &msg.Length,
|
|
|
|
FileEncSha256: msg.fileEncSha256,
|
|
|
|
FileSha256: msg.fileSha256,
|
|
|
|
FileLength: &msg.fileLength,
|
|
|
|
Mimetype: &msg.Type,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
Ptt: &msg.Ptt,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Download is the function to retrieve media data. The media gets downloaded, validated and returned.
|
|
|
|
*/
|
|
|
|
func (m *AudioMessage) Download() ([]byte, error) {
|
|
|
|
return Download(m.url, m.mediaKey, MediaAudio, int(m.fileLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
DocumentMessage represents a document message. Unexported fields are needed for media up/downloading and media
|
|
|
|
validation. Provide a io.Reader as Content for message sending.
|
|
|
|
*/
|
|
|
|
type DocumentMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
Title string
|
|
|
|
PageCount uint32
|
|
|
|
Type string
|
|
|
|
FileName string
|
|
|
|
Thumbnail []byte
|
|
|
|
Content io.Reader
|
|
|
|
url string
|
|
|
|
mediaKey []byte
|
|
|
|
fileEncSha256 []byte
|
|
|
|
fileSha256 []byte
|
|
|
|
fileLength uint64
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
|
|
|
|
doc := msg.GetMessage().GetDocumentMessage()
|
|
|
|
|
|
|
|
documentMessage := DocumentMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
Title: doc.GetTitle(),
|
|
|
|
PageCount: doc.GetPageCount(),
|
|
|
|
Type: doc.GetMimetype(),
|
|
|
|
FileName: doc.GetFileName(),
|
|
|
|
Thumbnail: doc.GetJpegThumbnail(),
|
|
|
|
url: doc.GetUrl(),
|
|
|
|
mediaKey: doc.GetMediaKey(),
|
|
|
|
fileEncSha256: doc.GetFileEncSha256(),
|
|
|
|
fileSha256: doc.GetFileSha256(),
|
|
|
|
fileLength: doc.GetFileLength(),
|
|
|
|
ContextInfo: getMessageContext(doc.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return documentMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDocumentProto(msg DocumentMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
DocumentMessage: &proto.DocumentMessage{
|
|
|
|
JpegThumbnail: msg.Thumbnail,
|
|
|
|
Url: &msg.url,
|
|
|
|
MediaKey: msg.mediaKey,
|
|
|
|
FileEncSha256: msg.fileEncSha256,
|
|
|
|
FileSha256: msg.fileSha256,
|
|
|
|
FileLength: &msg.fileLength,
|
|
|
|
PageCount: &msg.PageCount,
|
|
|
|
Title: &msg.Title,
|
|
|
|
Mimetype: &msg.Type,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Download is the function to retrieve media data. The media gets downloaded, validated and returned.
|
|
|
|
*/
|
|
|
|
func (m *DocumentMessage) Download() ([]byte, error) {
|
|
|
|
return Download(m.url, m.mediaKey, MediaDocument, int(m.fileLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
LocationMessage represents a location message
|
|
|
|
*/
|
|
|
|
type LocationMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
DegreesLatitude float64
|
|
|
|
DegreesLongitude float64
|
|
|
|
Name string
|
|
|
|
Address string
|
|
|
|
Url string
|
|
|
|
JpegThumbnail []byte
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetLocationMessage(msg *proto.WebMessageInfo) LocationMessage {
|
|
|
|
loc := msg.GetMessage().GetLocationMessage()
|
|
|
|
|
|
|
|
locationMessage := LocationMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
DegreesLatitude: loc.GetDegreesLatitude(),
|
|
|
|
DegreesLongitude: loc.GetDegreesLongitude(),
|
|
|
|
Name: loc.GetName(),
|
|
|
|
Address: loc.GetAddress(),
|
|
|
|
Url: loc.GetUrl(),
|
|
|
|
JpegThumbnail: loc.GetJpegThumbnail(),
|
|
|
|
ContextInfo: getMessageContext(loc.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return locationMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetLocationProto(msg LocationMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
LocationMessage: &proto.LocationMessage{
|
|
|
|
DegreesLatitude: &msg.DegreesLatitude,
|
|
|
|
DegreesLongitude: &msg.DegreesLongitude,
|
|
|
|
Name: &msg.Name,
|
|
|
|
Address: &msg.Address,
|
|
|
|
Url: &msg.Url,
|
|
|
|
JpegThumbnail: msg.JpegThumbnail,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
LiveLocationMessage represents a live location message
|
|
|
|
*/
|
|
|
|
type LiveLocationMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
DegreesLatitude float64
|
|
|
|
DegreesLongitude float64
|
|
|
|
AccuracyInMeters uint32
|
|
|
|
SpeedInMps float32
|
|
|
|
DegreesClockwiseFromMagneticNorth uint32
|
|
|
|
Caption string
|
|
|
|
SequenceNumber int64
|
|
|
|
JpegThumbnail []byte
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetLiveLocationMessage(msg *proto.WebMessageInfo) LiveLocationMessage {
|
|
|
|
loc := msg.GetMessage().GetLiveLocationMessage()
|
|
|
|
|
|
|
|
liveLocationMessage := LiveLocationMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
DegreesLatitude: loc.GetDegreesLatitude(),
|
|
|
|
DegreesLongitude: loc.GetDegreesLongitude(),
|
|
|
|
AccuracyInMeters: loc.GetAccuracyInMeters(),
|
|
|
|
SpeedInMps: loc.GetSpeedInMps(),
|
|
|
|
DegreesClockwiseFromMagneticNorth: loc.GetDegreesClockwiseFromMagneticNorth(),
|
|
|
|
Caption: loc.GetCaption(),
|
|
|
|
SequenceNumber: loc.GetSequenceNumber(),
|
|
|
|
JpegThumbnail: loc.GetJpegThumbnail(),
|
|
|
|
ContextInfo: getMessageContext(loc.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return liveLocationMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetLiveLocationProto(msg LiveLocationMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
LiveLocationMessage: &proto.LiveLocationMessage{
|
|
|
|
DegreesLatitude: &msg.DegreesLatitude,
|
|
|
|
DegreesLongitude: &msg.DegreesLongitude,
|
|
|
|
AccuracyInMeters: &msg.AccuracyInMeters,
|
|
|
|
SpeedInMps: &msg.SpeedInMps,
|
|
|
|
DegreesClockwiseFromMagneticNorth: &msg.DegreesClockwiseFromMagneticNorth,
|
|
|
|
Caption: &msg.Caption,
|
|
|
|
SequenceNumber: &msg.SequenceNumber,
|
|
|
|
JpegThumbnail: msg.JpegThumbnail,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
StickerMessage represents a sticker message.
|
|
|
|
*/
|
|
|
|
type StickerMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
|
|
|
|
Type string
|
|
|
|
Content io.Reader
|
|
|
|
url string
|
|
|
|
mediaKey []byte
|
|
|
|
fileEncSha256 []byte
|
|
|
|
fileSha256 []byte
|
|
|
|
fileLength uint64
|
|
|
|
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getStickerMessage(msg *proto.WebMessageInfo) StickerMessage {
|
|
|
|
sticker := msg.GetMessage().GetStickerMessage()
|
|
|
|
|
|
|
|
stickerMessage := StickerMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
url: sticker.GetUrl(),
|
|
|
|
mediaKey: sticker.GetMediaKey(),
|
|
|
|
Type: sticker.GetMimetype(),
|
|
|
|
fileEncSha256: sticker.GetFileEncSha256(),
|
|
|
|
fileSha256: sticker.GetFileSha256(),
|
|
|
|
fileLength: sticker.GetFileLength(),
|
|
|
|
ContextInfo: getMessageContext(sticker.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return stickerMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Download is the function to retrieve Sticker media data. The media gets downloaded, validated and returned.
|
|
|
|
*/
|
|
|
|
|
|
|
|
func (m *StickerMessage) Download() ([]byte, error) {
|
|
|
|
return Download(m.url, m.mediaKey, MediaImage, int(m.fileLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
ContactMessage represents a contact message.
|
|
|
|
*/
|
|
|
|
type ContactMessage struct {
|
|
|
|
Info MessageInfo
|
|
|
|
|
|
|
|
DisplayName string
|
|
|
|
Vcard string
|
|
|
|
|
|
|
|
ContextInfo ContextInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func getContactMessage(msg *proto.WebMessageInfo) ContactMessage {
|
|
|
|
contact := msg.GetMessage().GetContactMessage()
|
|
|
|
|
|
|
|
contactMessage := ContactMessage{
|
|
|
|
Info: getMessageInfo(msg),
|
|
|
|
|
|
|
|
DisplayName: contact.GetDisplayName(),
|
|
|
|
Vcard: contact.GetVcard(),
|
|
|
|
|
|
|
|
ContextInfo: getMessageContext(contact.GetContextInfo()),
|
|
|
|
}
|
|
|
|
|
|
|
|
return contactMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getContactMessageProto(msg ContactMessage) *proto.WebMessageInfo {
|
|
|
|
p := getInfoProto(&msg.Info)
|
|
|
|
contextInfo := getContextInfoProto(&msg.ContextInfo)
|
|
|
|
|
|
|
|
p.Message = &proto.Message{
|
|
|
|
ContactMessage: &proto.ContactMessage{
|
|
|
|
DisplayName: &msg.DisplayName,
|
|
|
|
Vcard: &msg.Vcard,
|
|
|
|
ContextInfo: contextInfo,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
|
|
|
case msg.GetMessage().GetAudioMessage() != nil:
|
|
|
|
return getAudioMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetImageMessage() != nil:
|
|
|
|
return getImageMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetVideoMessage() != nil:
|
|
|
|
return getVideoMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetDocumentMessage() != nil:
|
|
|
|
return getDocumentMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetConversation() != "":
|
|
|
|
return getTextMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetExtendedTextMessage() != nil:
|
|
|
|
return getTextMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetLocationMessage() != nil:
|
|
|
|
return GetLocationMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetLiveLocationMessage() != nil:
|
|
|
|
return GetLiveLocationMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetStickerMessage() != nil:
|
|
|
|
return getStickerMessage(msg)
|
|
|
|
|
|
|
|
case msg.GetMessage().GetContactMessage() != nil:
|
|
|
|
return getContactMessage(msg)
|
|
|
|
|
|
|
|
default:
|
|
|
|
//cannot match message
|
|
|
|
return ErrMessageTypeNotImplemented
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
BatteryMessage represents a battery level and charging state.
|
|
|
|
*/
|
|
|
|
type BatteryMessage struct {
|
|
|
|
Plugged bool
|
|
|
|
Powersave bool
|
|
|
|
Percentage int
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBatteryMessage(msg map[string]string) BatteryMessage {
|
|
|
|
plugged, _ := strconv.ParseBool(msg["live"])
|
|
|
|
powersave, _ := strconv.ParseBool(msg["powersave"])
|
|
|
|
percentage, _ := strconv.Atoi(msg["value"])
|
|
|
|
batteryMessage := BatteryMessage{
|
|
|
|
Plugged: plugged,
|
|
|
|
Powersave: powersave,
|
|
|
|
Percentage: percentage,
|
|
|
|
}
|
|
|
|
|
|
|
|
return batteryMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func getNewContact(msg map[string]string) Contact {
|
|
|
|
contact := Contact{
|
|
|
|
Jid: msg["jid"],
|
|
|
|
Notify: msg["notify"],
|
|
|
|
}
|
|
|
|
|
|
|
|
return contact
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseNodeMessage(msg binary.Node) interface{} {
|
|
|
|
switch msg.Description {
|
|
|
|
case "battery":
|
|
|
|
return getBatteryMessage(msg.Attributes)
|
|
|
|
case "user":
|
|
|
|
return getNewContact(msg.Attributes)
|
|
|
|
default:
|
|
|
|
//cannot match message
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|