message.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. package whatsapp
  2. import (
  3. "encoding/hex"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/Rhymen/go-whatsapp/binary"
  7. "github.com/Rhymen/go-whatsapp/binary/proto"
  8. "io"
  9. "math/rand"
  10. "strconv"
  11. "strings"
  12. "time"
  13. )
  14. type MediaType string
  15. const (
  16. MediaImage MediaType = "WhatsApp Image Keys"
  17. MediaVideo MediaType = "WhatsApp Video Keys"
  18. MediaAudio MediaType = "WhatsApp Audio Keys"
  19. MediaDocument MediaType = "WhatsApp Document Keys"
  20. )
  21. func (wac *Conn) Send(msg interface{}) error {
  22. var err error
  23. var ch <-chan string
  24. switch m := msg.(type) {
  25. case *proto.WebMessageInfo:
  26. ch, err = wac.sendProto(m)
  27. case TextMessage:
  28. ch, err = wac.sendProto(getTextProto(m))
  29. case ImageMessage:
  30. m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaImage)
  31. if err != nil {
  32. return fmt.Errorf("image upload failed: %v", err)
  33. }
  34. ch, err = wac.sendProto(getImageProto(m))
  35. case VideoMessage:
  36. m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaVideo)
  37. if err != nil {
  38. return fmt.Errorf("video upload failed: %v", err)
  39. }
  40. ch, err = wac.sendProto(getVideoProto(m))
  41. case DocumentMessage:
  42. m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaDocument)
  43. if err != nil {
  44. return fmt.Errorf("document upload failed: %v", err)
  45. }
  46. ch, err = wac.sendProto(getDocumentProto(m))
  47. case AudioMessage:
  48. m.url, m.mediaKey, m.fileEncSha256, m.fileSha256, m.fileLength, err = wac.Upload(m.Content, MediaAudio)
  49. if err != nil {
  50. return fmt.Errorf("audio upload failed: %v", err)
  51. }
  52. ch, err = wac.sendProto(getAudioProto(m))
  53. default:
  54. return fmt.Errorf("cannot match type %T, use message types declared in the package", msg)
  55. }
  56. if err != nil {
  57. return fmt.Errorf("could not send proto: %v", err)
  58. }
  59. select {
  60. case response := <-ch:
  61. var resp map[string]interface{}
  62. if err = json.Unmarshal([]byte(response), &resp); err != nil {
  63. return fmt.Errorf("error decoding sending response: %v\n", err)
  64. }
  65. if int(resp["status"].(float64)) != 200 {
  66. return fmt.Errorf("message sending responded with %d", resp["status"])
  67. }
  68. case <-time.After(wac.msgTimeout):
  69. return fmt.Errorf("sending message timed out")
  70. }
  71. return nil
  72. }
  73. func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) {
  74. n := binary.Node{
  75. Description: "action",
  76. Attributes: map[string]string{
  77. "type": "relay",
  78. "epoch": strconv.Itoa(wac.msgCount),
  79. },
  80. Content: []interface{}{p},
  81. }
  82. return wac.writeBinary(n, message, ignore, p.Key.GetId())
  83. }
  84. func init() {
  85. rand.Seed(time.Now().UTC().UnixNano())
  86. }
  87. /*
  88. MessageInfo contains general message information. It is part of every of every message type.
  89. */
  90. type MessageInfo struct {
  91. Id string
  92. RemoteJid string
  93. SenderJid string
  94. FromMe bool
  95. Timestamp uint64
  96. PushName string
  97. Status MessageStatus
  98. QuotedMessageID string
  99. Source *proto.WebMessageInfo
  100. }
  101. type MessageStatus int
  102. const (
  103. Error MessageStatus = 0
  104. Pending = 1
  105. ServerAck = 2
  106. DeliveryAck = 3
  107. Read = 4
  108. Played = 5
  109. )
  110. func getMessageInfo(msg *proto.WebMessageInfo) MessageInfo {
  111. return MessageInfo{
  112. Id: msg.GetKey().GetId(),
  113. RemoteJid: msg.GetKey().GetRemoteJid(),
  114. SenderJid: msg.GetKey().GetParticipant(),
  115. FromMe: msg.GetKey().GetFromMe(),
  116. Timestamp: msg.GetMessageTimestamp(),
  117. Status: MessageStatus(msg.GetStatus()),
  118. PushName: msg.GetPushName(),
  119. Source: msg,
  120. }
  121. }
  122. func getInfoProto(info *MessageInfo) *proto.WebMessageInfo {
  123. if info.Id == "" || len(info.Id) < 2 {
  124. b := make([]byte, 10)
  125. rand.Read(b)
  126. info.Id = strings.ToUpper(hex.EncodeToString(b))
  127. }
  128. if info.Timestamp == 0 {
  129. info.Timestamp = uint64(time.Now().Unix())
  130. }
  131. info.FromMe = true
  132. status := proto.WebMessageInfo_STATUS(info.Status)
  133. return &proto.WebMessageInfo{
  134. Key: &proto.MessageKey{
  135. FromMe: &info.FromMe,
  136. RemoteJid: &info.RemoteJid,
  137. Id: &info.Id,
  138. },
  139. MessageTimestamp: &info.Timestamp,
  140. Status: &status,
  141. }
  142. }
  143. /*
  144. TextMessage represents a text message.
  145. */
  146. type TextMessage struct {
  147. Info MessageInfo
  148. Text string
  149. }
  150. func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
  151. text := TextMessage{Info: getMessageInfo(msg)}
  152. if m := msg.GetMessage().GetExtendedTextMessage(); m != nil {
  153. text.Text = m.GetText()
  154. text.Info.QuotedMessageID = m.GetContextInfo().GetStanzaId()
  155. } else {
  156. text.Text = msg.GetMessage().GetConversation()
  157. }
  158. return text
  159. }
  160. func getTextProto(msg TextMessage) *proto.WebMessageInfo {
  161. p := getInfoProto(&msg.Info)
  162. p.Message = &proto.Message{
  163. Conversation: &msg.Text,
  164. }
  165. return p
  166. }
  167. /*
  168. ImageMessage represents a image message. Unexported fields are needed for media up/downloading and media validation.
  169. Provide a io.Reader as Content for message sending.
  170. */
  171. type ImageMessage struct {
  172. Info MessageInfo
  173. Caption string
  174. Thumbnail []byte
  175. Type string
  176. Content io.Reader
  177. url string
  178. mediaKey []byte
  179. fileEncSha256 []byte
  180. fileSha256 []byte
  181. fileLength uint64
  182. }
  183. func getImageMessage(msg *proto.WebMessageInfo) ImageMessage {
  184. image := msg.GetMessage().GetImageMessage()
  185. return ImageMessage{
  186. Info: getMessageInfo(msg),
  187. Caption: image.GetCaption(),
  188. Thumbnail: image.GetJpegThumbnail(),
  189. url: image.GetUrl(),
  190. mediaKey: image.GetMediaKey(),
  191. Type: image.GetMimetype(),
  192. fileEncSha256: image.GetFileEncSha256(),
  193. fileSha256: image.GetFileSha256(),
  194. fileLength: image.GetFileLength(),
  195. }
  196. }
  197. func getImageProto(msg ImageMessage) *proto.WebMessageInfo {
  198. p := getInfoProto(&msg.Info)
  199. p.Message = &proto.Message{
  200. ImageMessage: &proto.ImageMessage{
  201. Caption: &msg.Caption,
  202. JpegThumbnail: msg.Thumbnail,
  203. Url: &msg.url,
  204. MediaKey: msg.mediaKey,
  205. Mimetype: &msg.Type,
  206. FileEncSha256: msg.fileEncSha256,
  207. FileSha256: msg.fileSha256,
  208. FileLength: &msg.fileLength,
  209. },
  210. }
  211. return p
  212. }
  213. /*
  214. Download is the function to retrieve media data. The media gets downloaded, validated and returned.
  215. */
  216. func (m *ImageMessage) Download() ([]byte, error) {
  217. return Download(m.url, m.mediaKey, MediaImage, int(m.fileLength))
  218. }
  219. /*
  220. VideoMessage represents a video message. Unexported fields are needed for media up/downloading and media validation.
  221. Provide a io.Reader as Content for message sending.
  222. */
  223. type VideoMessage struct {
  224. Info MessageInfo
  225. Caption string
  226. Thumbnail []byte
  227. Length uint32
  228. Type string
  229. Content io.Reader
  230. url string
  231. mediaKey []byte
  232. fileEncSha256 []byte
  233. fileSha256 []byte
  234. fileLength uint64
  235. }
  236. func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
  237. vid := msg.GetMessage().GetVideoMessage()
  238. return VideoMessage{
  239. Info: getMessageInfo(msg),
  240. Caption: vid.GetCaption(),
  241. Thumbnail: vid.GetJpegThumbnail(),
  242. url: vid.GetUrl(),
  243. mediaKey: vid.GetMediaKey(),
  244. Length: vid.GetSeconds(),
  245. Type: vid.GetMimetype(),
  246. fileEncSha256: vid.GetFileEncSha256(),
  247. fileSha256: vid.GetFileSha256(),
  248. fileLength: vid.GetFileLength(),
  249. }
  250. }
  251. func getVideoProto(msg VideoMessage) *proto.WebMessageInfo {
  252. p := getInfoProto(&msg.Info)
  253. p.Message = &proto.Message{
  254. VideoMessage: &proto.VideoMessage{
  255. Caption: &msg.Caption,
  256. JpegThumbnail: msg.Thumbnail,
  257. Url: &msg.url,
  258. MediaKey: msg.mediaKey,
  259. Seconds: &msg.Length,
  260. FileEncSha256: msg.fileEncSha256,
  261. FileSha256: msg.fileSha256,
  262. FileLength: &msg.fileLength,
  263. Mimetype: &msg.Type,
  264. },
  265. }
  266. return p
  267. }
  268. /*
  269. Download is the function to retrieve media data. The media gets downloaded, validated and returned.
  270. */
  271. func (m *VideoMessage) Download() ([]byte, error) {
  272. return Download(m.url, m.mediaKey, MediaVideo, int(m.fileLength))
  273. }
  274. /*
  275. AudioMessage represents a audio message. Unexported fields are needed for media up/downloading and media validation.
  276. Provide a io.Reader as Content for message sending.
  277. */
  278. type AudioMessage struct {
  279. Info MessageInfo
  280. Length uint32
  281. Type string
  282. Content io.Reader
  283. url string
  284. mediaKey []byte
  285. fileEncSha256 []byte
  286. fileSha256 []byte
  287. fileLength uint64
  288. }
  289. func getAudioMessage(msg *proto.WebMessageInfo) AudioMessage {
  290. aud := msg.GetMessage().GetAudioMessage()
  291. return AudioMessage{
  292. Info: getMessageInfo(msg),
  293. url: aud.GetUrl(),
  294. mediaKey: aud.GetMediaKey(),
  295. Length: aud.GetSeconds(),
  296. Type: aud.GetMimetype(),
  297. fileEncSha256: aud.GetFileEncSha256(),
  298. fileSha256: aud.GetFileSha256(),
  299. fileLength: aud.GetFileLength(),
  300. }
  301. }
  302. func getAudioProto(msg AudioMessage) *proto.WebMessageInfo {
  303. p := getInfoProto(&msg.Info)
  304. p.Message = &proto.Message{
  305. AudioMessage: &proto.AudioMessage{
  306. Url: &msg.url,
  307. MediaKey: msg.mediaKey,
  308. Seconds: &msg.Length,
  309. FileEncSha256: msg.fileEncSha256,
  310. FileSha256: msg.fileSha256,
  311. FileLength: &msg.fileLength,
  312. Mimetype: &msg.Type,
  313. },
  314. }
  315. return p
  316. }
  317. /*
  318. Download is the function to retrieve media data. The media gets downloaded, validated and returned.
  319. */
  320. func (m *AudioMessage) Download() ([]byte, error) {
  321. return Download(m.url, m.mediaKey, MediaAudio, int(m.fileLength))
  322. }
  323. /*
  324. DocumentMessage represents a document message. Unexported fields are needed for media up/downloading and media
  325. validation. Provide a io.Reader as Content for message sending.
  326. */
  327. type DocumentMessage struct {
  328. Info MessageInfo
  329. Title string
  330. PageCount uint32
  331. Type string
  332. Thumbnail []byte
  333. Content io.Reader
  334. url string
  335. mediaKey []byte
  336. fileEncSha256 []byte
  337. fileSha256 []byte
  338. fileLength uint64
  339. }
  340. func getDocumentMessage(msg *proto.WebMessageInfo) DocumentMessage {
  341. doc := msg.GetMessage().GetDocumentMessage()
  342. return DocumentMessage{
  343. Info: getMessageInfo(msg),
  344. Thumbnail: doc.GetJpegThumbnail(),
  345. url: doc.GetUrl(),
  346. mediaKey: doc.GetMediaKey(),
  347. fileEncSha256: doc.GetFileEncSha256(),
  348. fileSha256: doc.GetFileSha256(),
  349. fileLength: doc.GetFileLength(),
  350. PageCount: doc.GetPageCount(),
  351. Title: doc.GetTitle(),
  352. Type: doc.GetMimetype(),
  353. }
  354. }
  355. func getDocumentProto(msg DocumentMessage) *proto.WebMessageInfo {
  356. p := getInfoProto(&msg.Info)
  357. p.Message = &proto.Message{
  358. DocumentMessage: &proto.DocumentMessage{
  359. JpegThumbnail: msg.Thumbnail,
  360. Url: &msg.url,
  361. MediaKey: msg.mediaKey,
  362. FileEncSha256: msg.fileEncSha256,
  363. FileSha256: msg.fileSha256,
  364. FileLength: &msg.fileLength,
  365. PageCount: &msg.PageCount,
  366. Title: &msg.Title,
  367. Mimetype: &msg.Type,
  368. },
  369. }
  370. return p
  371. }
  372. /*
  373. Download is the function to retrieve media data. The media gets downloaded, validated and returned.
  374. */
  375. func (m *DocumentMessage) Download() ([]byte, error) {
  376. return Download(m.url, m.mediaKey, MediaDocument, int(m.fileLength))
  377. }
  378. func parseProtoMessage(msg *proto.WebMessageInfo) interface{} {
  379. switch {
  380. case msg.GetMessage().GetAudioMessage() != nil:
  381. return getAudioMessage(msg)
  382. case msg.GetMessage().GetImageMessage() != nil:
  383. return getImageMessage(msg)
  384. case msg.GetMessage().GetVideoMessage() != nil:
  385. return getVideoMessage(msg)
  386. case msg.GetMessage().GetDocumentMessage() != nil:
  387. return getDocumentMessage(msg)
  388. case msg.GetMessage().GetConversation() != "":
  389. return getTextMessage(msg)
  390. case msg.GetMessage().GetExtendedTextMessage() != nil:
  391. return getTextMessage(msg)
  392. default:
  393. //cannot match message
  394. }
  395. return nil
  396. }