888 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			888 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 | 
