123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- package whatsapp
- import (
- "encoding/hex"
- "encoding/json"
- "fmt"
- "github.com/Rhymen/go-whatsapp/binary"
- "github.com/Rhymen/go-whatsapp/binary/proto"
- "io"
- "math/rand"
- "strconv"
- "strings"
- "time"
- )
- 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{}) error {
- var err error
- var ch <-chan string
- switch m := msg.(type) {
- case *proto.WebMessageInfo:
- ch, err = wac.sendProto(m)
- case TextMessage:
- ch, err = wac.sendProto(getTextProto(m))
- case ImageMessage:
- m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaImage)
- if err != nil {
- return fmt.Errorf("image upload failed: %v", err)
- }
- ch, err = wac.sendProto(getImageProto(m))
- case VideoMessage:
- m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaVideo)
- if err != nil {
- return fmt.Errorf("video upload failed: %v", err)
- }
- ch, err = wac.sendProto(getVideoProto(m))
- case DocumentMessage:
- m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaDocument)
- if err != nil {
- return fmt.Errorf("document upload failed: %v", err)
- }
- ch, err = wac.sendProto(getDocumentProto(m))
- case AudioMessage:
- m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaAudio)
- if err != nil {
- return fmt.Errorf("audio upload failed: %v", err)
- }
- ch, err = wac.sendProto(getAudioProto(m))
- default:
- return fmt.Errorf("cannot match type %T, use message types declared in the package", msg)
- }
- 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 sending response: %v\n", err)
- }
- if int(resp["status"].(float64)) != 200 {
- return fmt.Errorf("message sending responded with %d", resp["status"])
- }
- case <-time.After(wac.msgTimeout):
- return fmt.Errorf("sending message timed out")
- }
- return 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())
- }
- 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
- QuotedMessageID string
- 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.GetKey().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_STATUS(info.Status)
- return &proto.WebMessageInfo{
- Key: &proto.MessageKey{
- FromMe: &info.FromMe,
- RemoteJid: &info.RemoteJid,
- Id: &info.Id,
- },
- MessageTimestamp: &info.Timestamp,
- Status: &status,
- }
- }
- /*
- TextMessage represents a text message.
- */
- type TextMessage struct {
- Info MessageInfo
- Text string
- }
- func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
- text := TextMessage{Info: getMessageInfo(msg)}
- if m := msg.GetMessage().GetExtendedTextMessage(); m != nil {
- text.Text = m.GetText()
- text.Info.QuotedMessageID = m.GetContextInfo().GetStanzaId()
- } else {
- text.Text = msg.GetMessage().GetConversation()
- }
- return text
- }
- func getTextProto(msg TextMessage) *proto.WebMessageInfo {
- p := getInfoProto(&msg.Info)
- p.Message = &proto.Message{
- Conversation: &msg.Text,
- }
- 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
- }
- func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
- image := msg.GetMessage().GetImageMessage()
- return 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(),
- }
- }
- func getImageProto(msg ImageMessage) *proto.WebMessageInfo {
- p := getInfoProto(&msg.Info)
- 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,
- },
- }
- 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
- url string
- mediaKey []byte
- fileEncSha256 []byte
- fileSha256 []byte
- fileLength uint64
- }
- func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
- vid := msg.GetMessage().GetVideoMessage()
- return VideoMessage{
- Info: getMessageInfo(msg),
- Caption: vid.GetCaption(),
- Thumbnail: vid.GetJpegThumbnail(),
- url: vid.GetUrl(),
- mediaKey: vid.GetMediaKey(),
- Length: vid.GetSeconds(),
- Type: vid.GetMimetype(),
- fileEncSha256: vid.GetFileEncSha256(),
- fileSha256: vid.GetFileSha256(),
- fileLength: vid.GetFileLength(),
- }
- }
- func getVideoProto(msg VideoMessage) *proto.WebMessageInfo {
- p := getInfoProto(&msg.Info)
- p.Message = &proto.Message{
- VideoMessage: &proto.VideoMessage{
- Caption: &msg.Caption,
- JpegThumbnail: msg.Thumbnail,
- Url: &msg.url,
- MediaKey: msg.mediaKey,
- Seconds: &msg.Length,
- FileEncSha256: msg.fileEncSha256,
- FileSha256: msg.fileSha256,
- FileLength: &msg.fileLength,
- Mimetype: &msg.Type,
- },
- }
- 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
- url string
- mediaKey []byte
- fileEncSha256 []byte
- fileSha256 []byte
- fileLength uint64
- }
- func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
- aud := msg.GetMessage().GetAudioMessage()
- return 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(),
- }
- }
- func getAudioProto(msg AudioMessage) *proto.WebMessageInfo {
- p := getInfoProto(&msg.Info)
- 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,
- },
- }
- 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
- Thumbnail []byte
- Content io.Reader
- url string
- mediaKey []byte
- fileEncSha256 []byte
- fileSha256 []byte
- fileLength uint64
- }
- func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
- doc := msg.GetMessage().GetDocumentMessage()
- return DocumentMessage{
- Info: getMessageInfo(msg),
- Thumbnail: doc.GetJpegThumbnail(),
- url: doc.GetUrl(),
- mediaKey: doc.GetMediaKey(),
- fileEncSha256: doc.GetFileEncSha256(),
- fileSha256: doc.GetFileSha256(),
- fileLength: doc.GetFileLength(),
- PageCount: doc.GetPageCount(),
- Title: doc.GetTitle(),
- Type: doc.GetMimetype(),
- }
- }
- func getDocumentProto(msg DocumentMessage) *proto.WebMessageInfo {
- p := getInfoProto(&msg.Info)
- 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,
- },
- }
- 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))
- }
- 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)
- default:
- //cannot match message
- }
- return nil
- }
|