Files
whatscli/messages/session_manager.go

1008 lines
28 KiB
Go

package messages
import (
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"mime"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/Rhymen/go-whatsapp"
"github.com/gabriel-vasile/mimetype"
"github.com/gdamore/tcell/v2"
"github.com/gen2brain/beeep"
"github.com/normen/whatscli/config"
"github.com/normen/whatscli/qrcode"
"github.com/rivo/tview"
"mvdan.cc/xurls/v2"
)
// SessionManager deals with the connection and receives commands from the UI
// it updates the UI accordingly
type SessionManager struct {
db *MessageDatabase
currentReceiver string // currently selected chat for message handling
uiHandler UiMessageHandler
connection *whatsapp.Conn
BatteryChannel chan BatteryMsg
StatusChannel chan StatusMsg
CommandChannel chan Command
ChatChannel chan whatsapp.Chat
ContactChannel chan whatsapp.Contact
TextChannel chan whatsapp.TextMessage
OtherChannel chan interface{}
statusInfo SessionStatus
lastSent time.Time
started bool
}
// initialize the SessionManager
func (sm *SessionManager) Init(handler UiMessageHandler) {
sm.db = &MessageDatabase{}
sm.db.Init()
sm.uiHandler = handler
sm.BatteryChannel = make(chan BatteryMsg, 10)
sm.StatusChannel = make(chan StatusMsg, 10)
sm.CommandChannel = make(chan Command, 10)
sm.ChatChannel = make(chan whatsapp.Chat, 10)
sm.ContactChannel = make(chan whatsapp.Contact, 10)
sm.TextChannel = make(chan whatsapp.TextMessage, 10)
sm.OtherChannel = make(chan interface{}, 10)
}
// starts the receiver and message handling go routine
func (sm *SessionManager) StartManager() error {
if sm.started {
return errors.New("session manager running, send commands to control")
}
sm.started = true
go sm.runManager()
return nil
}
func (sm *SessionManager) runManager() error {
var wac = sm.getConnection()
err := sm.loginWithConnection(wac)
if err != nil {
sm.uiHandler.PrintError(err)
}
wac.AddHandler(sm)
for sm.started == true {
select {
case msg := <-sm.TextChannel:
didNew := sm.db.AddTextMessage(&msg)
if msg.Info.RemoteJid == sm.currentReceiver {
if didNew {
sm.uiHandler.NewMessage(sm.createMessage(&msg))
} else {
screen := sm.getMessages(sm.currentReceiver)
sm.uiHandler.NewScreen(screen)
}
// notify if chat is in focus and we didn't send a message recently
// TODO: move notify to UI
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
if int64(msg.Info.Timestamp) > sm.lastSent.Unix()+config.Config.General.NotificationTimeout {
sm.db.NewUnreadChat(msg.Info.RemoteJid)
if !msg.Info.FromMe {
err := notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text)
if err != nil {
sm.uiHandler.PrintError(err)
}
}
}
}
} else {
// notify if message is younger than 30 sec and not in focus
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
sm.db.NewUnreadChat(msg.Info.RemoteJid)
if !msg.Info.FromMe {
err := notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text)
if err != nil {
sm.uiHandler.PrintError(err)
}
}
}
}
sm.uiHandler.SetChats(sm.db.GetChatIds())
case other := <-sm.OtherChannel:
sm.db.AddOtherMessage(&other)
case c := <-sm.ContactChannel:
contact := Contact{
c.Jid,
c.Name,
c.Short,
}
if contact.Name == "" && c.Notify != "" {
contact.Name = c.Notify
}
if contact.Short == "" && c.Notify != "" {
contact.Short = c.Notify
}
sm.db.AddContact(contact)
sm.uiHandler.SetChats(sm.db.GetChatIds())
case c := <-sm.ChatChannel:
if c.IsMarkedSpam == "false" {
isGroup := strings.Contains(c.Jid, GROUPSUFFIX)
unread, _ := strconv.ParseInt(c.Unread, 10, 0)
last, _ := strconv.ParseInt(c.LastMessageTime, 10, 64)
chat := Chat{
c.Jid,
isGroup,
c.Name,
int(unread),
last,
}
sm.db.AddChat(chat)
sm.uiHandler.SetChats(sm.db.GetChatIds())
}
case command := <-sm.CommandChannel:
sm.execCommand(command)
case batteryMsg := <-sm.BatteryChannel:
sm.statusInfo.BatteryLoading = batteryMsg.loading
sm.statusInfo.BatteryPowersave = batteryMsg.powersave
sm.statusInfo.BatteryCharge = batteryMsg.charge
sm.uiHandler.SetStatus(sm.statusInfo)
case statusMsg := <-sm.StatusChannel:
prevStatus := sm.statusInfo.Connected
if statusMsg.err != nil {
} else {
sm.statusInfo.Connected = statusMsg.connected
}
wac := sm.getConnection()
connected := wac.GetConnected()
sm.statusInfo.Connected = connected
sm.uiHandler.SetStatus(sm.statusInfo)
if prevStatus != sm.statusInfo.Connected {
if sm.statusInfo.Connected {
sm.uiHandler.PrintText("connected")
} else {
sm.uiHandler.PrintText("disconnected")
}
}
}
}
fmt.Fprintln(sm.uiHandler.GetWriter(), "closing the receiver")
wac.Disconnect()
return nil
}
// set the currently selected chat
func (sm *SessionManager) setCurrentReceiver(id string) {
sm.currentReceiver = id
screen := sm.getMessages(id)
sm.uiHandler.NewScreen(screen)
}
// gets an existing connection or creates one
func (sm *SessionManager) getConnection() *whatsapp.Conn {
var wac *whatsapp.Conn
if sm.connection == nil {
options := &whatsapp.Options{
Timeout: 5 * time.Second,
LongClientName: "WhatsCLI Client",
ShortClientName: "whatscli",
}
wacc, err := whatsapp.NewConnWithOptions(options)
if err != nil {
return nil
}
wac = wacc
sm.connection = wac
wac.SetClientVersion(2, 2142, 12)
} else {
wac = sm.connection
}
return wac
}
// login logs in the user. It ries to see if a session already exists. If not, tries to create a
// new one using qr scanned on the terminal.
func (sm *SessionManager) login() error {
return sm.loginWithConnection(sm.getConnection())
}
// loginWithConnection logs in the user using a provided connection. It ries to see if a session already exists. If not, tries to create a
// new one using qr scanned on the terminal.
func (sm *SessionManager) loginWithConnection(wac *whatsapp.Conn) error {
sm.uiHandler.PrintText("connecting..")
if wac != nil && wac.GetConnected() {
wac.Disconnect()
sm.StatusChannel <- StatusMsg{false, nil}
}
//load saved session
session, err := readSession()
if err == nil {
//restore session
session, err = wac.RestoreWithSession(session)
if err != nil {
return fmt.Errorf("restoring failed: %v\n", err)
}
} else {
//no saved session -> regular login
qr := make(chan string)
go func() {
terminal := qrcode.New()
terminal.SetOutput(tview.ANSIWriter(sm.uiHandler.GetWriter()))
terminal.Get(<-qr).Print()
}()
session, err = wac.Login(qr)
if err != nil {
return fmt.Errorf("error during login: %v\n", err)
}
}
//save session
err = writeSession(session)
if err != nil {
return fmt.Errorf("error saving session: %v\n", err)
}
//get initial battery state
sm.BatteryChannel <- BatteryMsg{
wac.Info.Battery,
wac.Info.Plugged,
false,
}
sm.StatusChannel <- StatusMsg{true, nil}
return nil
}
// disconnects the session
func (sm *SessionManager) disconnect() error {
wac := sm.getConnection()
var err error
if wac != nil && wac.GetConnected() {
_, err = wac.Disconnect()
}
sm.StatusChannel <- StatusMsg{false, err}
return err
}
// logout logs out the user, deletes session file
func (ub *SessionManager) logout() error {
err := ub.getConnection().Logout()
ub.StatusChannel <- StatusMsg{false, err}
ub.uiHandler.PrintText("removing login data..")
return removeSession()
}
// executes a command
func (sm *SessionManager) execCommand(command Command) {
cmd := command.Name
switch cmd {
default:
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Unknown command: [-]" + cmd)
case "backlog":
if sm.currentReceiver != "" {
count := config.Config.General.BacklogMsgQuantity
if currentMsgs, ok := sm.db.textMessages[sm.currentReceiver]; ok {
if len(currentMsgs) > 0 {
firstMsg := currentMsgs[0]
go sm.getConnection().LoadChatMessages(sm.currentReceiver, count, firstMsg.Info.Id, firstMsg.Info.FromMe, false, sm)
}
} else {
go sm.getConnection().LoadChatMessages(sm.currentReceiver, count, "", false, false, sm)
}
} else {
sm.printCommandUsage("backlog", "-> only works in a chat")
}
case "login":
sm.uiHandler.PrintError(sm.login())
case "connect":
sm.uiHandler.PrintError(sm.login())
case "disconnect":
sm.uiHandler.PrintError(sm.disconnect())
case "logout":
sm.uiHandler.PrintError(sm.logout())
case "send":
if checkParam(command.Params, 2) {
textParams := command.Params[1:]
text := strings.Join(textParams, " ")
sm.sendText(command.Params[0], text)
} else {
sm.printCommandUsage("send", "[chat-id[] [message text[]")
}
case "select":
if checkParam(command.Params, 1) {
sm.setCurrentReceiver(command.Params[0])
} else {
sm.printCommandUsage("select", "[chat-id[]")
}
case "read":
if sm.currentReceiver != "" {
// need to send message id, so get all (unread count)
// recent messages and send "read"
if chat, ok := sm.db.chats[sm.currentReceiver]; ok {
count := chat.Unread
msgs := sm.db.GetMessages(chat.Id)
length := len(msgs)
for idx, msg := range msgs {
if idx >= length-count {
sm.getConnection().Read(chat.Id, msg.Info.Id)
}
}
chat.Unread = 0
sm.db.chats[sm.currentReceiver] = chat
sm.uiHandler.SetChats(sm.db.GetChatIds())
}
} else {
sm.printCommandUsage("read", "-> only works in a chat")
}
case "info":
if checkParam(command.Params, 1) {
sm.uiHandler.PrintText(sm.db.GetMessageInfo(command.Params[0]))
} else {
sm.printCommandUsage("info", "[message-id[]")
}
case "download":
if checkParam(command.Params, 1) {
if path, err := sm.downloadMessage(command.Params[0], false); err != nil {
sm.uiHandler.PrintError(err)
} else {
sm.uiHandler.PrintText("[::d] -> " + path + "[::-]")
}
} else {
sm.printCommandUsage("download", "[message-id[]")
}
case "open":
if checkParam(command.Params, 1) {
if path, err := sm.downloadMessage(command.Params[0], true); err == nil {
sm.uiHandler.OpenFile(path)
} else {
sm.uiHandler.PrintError(err)
}
} else {
sm.printCommandUsage("open", "[message-id[]")
}
case "show":
if checkParam(command.Params, 1) {
if path, err := sm.downloadMessage(command.Params[0], true); err == nil {
sm.uiHandler.PrintFile(path)
} else {
sm.uiHandler.PrintError(err)
}
} else {
sm.printCommandUsage("show", "[message-id[]")
}
case "url":
if checkParam(command.Params, 1) {
if msg, ok := sm.db.messagesById[command.Params[0]]; ok {
urlParser := xurls.Relaxed()
url := urlParser.FindString(msg.Text)
if url != "" {
sm.uiHandler.OpenFile(url)
}
}
} else {
sm.printCommandUsage("url", "[message-id[]")
}
case "upload":
if sm.currentReceiver == "" {
sm.printCommandUsage("upload", "-> only works in a chat")
return
}
var err error
var mime *mimetype.MIME
var file *os.File
if checkParam(command.Params, 1) {
path := strings.Join(command.Params, " ")
if mime, err = mimetype.DetectFile(path); err == nil {
if file, err = os.Open(path); err == nil {
msg := whatsapp.DocumentMessage{
Info: whatsapp.MessageInfo{
RemoteJid: sm.currentReceiver,
},
Type: mime.String(),
FileName: filepath.Base(file.Name()),
}
wac := sm.getConnection()
sm.lastSent = time.Now()
_, err = wac.Send(msg)
}
}
} else {
sm.printCommandUsage("upload", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendimage":
if sm.currentReceiver == "" {
sm.printCommandUsage("sendimage", "-> only works in a chat")
return
}
var err error
var mime *mimetype.MIME
var file *os.File
if checkParam(command.Params, 1) {
path := strings.Join(command.Params, " ")
if mime, err = mimetype.DetectFile(path); err == nil {
if file, err = os.Open(path); err == nil {
msg := whatsapp.ImageMessage{
Info: whatsapp.MessageInfo{
RemoteJid: sm.currentReceiver,
},
Type: mime.String(),
Content: file,
}
wac := sm.getConnection()
sm.lastSent = time.Now()
_, err = wac.Send(msg)
}
}
} else {
sm.printCommandUsage("sendimage", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendvideo":
if sm.currentReceiver == "" {
sm.printCommandUsage("sendvideo", "-> only works in a chat")
return
}
var err error
var mime *mimetype.MIME
var file *os.File
if checkParam(command.Params, 1) {
path := strings.Join(command.Params, " ")
if mime, err = mimetype.DetectFile(path); err == nil {
if file, err = os.Open(path); err == nil {
msg := whatsapp.VideoMessage{
Info: whatsapp.MessageInfo{
RemoteJid: sm.currentReceiver,
},
Type: mime.String(),
Content: file,
}
wac := sm.getConnection()
sm.lastSent = time.Now()
_, err = wac.Send(msg)
}
}
} else {
sm.printCommandUsage("sendvideo", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendaudio":
if sm.currentReceiver == "" {
sm.printCommandUsage("sendaudio", "-> only works in a chat")
return
}
var err error
var mime *mimetype.MIME
var file *os.File
if checkParam(command.Params, 1) {
path := strings.Join(command.Params, " ")
if mime, err = mimetype.DetectFile(path); err == nil {
if file, err = os.Open(path); err == nil {
msg := whatsapp.AudioMessage{
Info: whatsapp.MessageInfo{
RemoteJid: sm.currentReceiver,
},
Type: mime.String(),
Content: file,
}
wac := sm.getConnection()
sm.lastSent = time.Now()
_, err = wac.Send(msg)
}
}
} else {
sm.printCommandUsage("sendaudio", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "revoke":
if checkParam(command.Params, 1) {
wac := sm.getConnection()
var revId string
var err error
if msgg, ok := sm.db.otherMessages[command.Params[0]]; ok {
switch msg := (*msgg).(type) {
default:
case whatsapp.ImageMessage:
revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
case whatsapp.DocumentMessage:
revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
case whatsapp.AudioMessage:
revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
case whatsapp.VideoMessage:
revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
}
} else {
if msg, ok := sm.db.messagesById[command.Params[0]]; ok {
revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
}
}
if err == nil {
sm.uiHandler.PrintText("revoked: " + revId)
}
sm.uiHandler.PrintError(err)
} else {
sm.printCommandUsage("revoke", "[message-id[]")
}
case "leave":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
wac := sm.getConnection()
var err error
_, err = wac.LeaveGroup(groupId)
if err == nil {
sm.uiHandler.PrintText("left group " + groupId)
}
sm.uiHandler.PrintError(err)
case "create":
if !checkParam(command.Params, 1) {
sm.printCommandUsage("create", "[user-id[] [user-id[] New Group Subject")
sm.printCommandUsage("create", "New Group Subject")
return
}
// first params are users if ending in CONTACTSUFFIX, rest is name
users := []string{}
idx := 0
size := len(command.Params)
for idx = 0; idx < size && strings.Index(command.Params[idx], CONTACTSUFFIX) > 0; idx++ {
users = append(users, command.Params[idx])
}
name := ""
if len(command.Params) > idx {
name = strings.Join(command.Params[idx:], " ")
}
wac := sm.getConnection()
var err error
var groupId <-chan string
groupId, err = wac.CreateGroup(name, users)
if err == nil {
sm.uiHandler.PrintText("creating new group " + name)
resultInfo := <-groupId
//{"status":200,"gid":"491600000009-0606000436@g.us","participants":[{"491700000000@c.us":{"code":"200"}},{"4917600000001@c.us":{"code": "200"}}]}
var result map[string]interface{}
json.Unmarshal([]byte(resultInfo), &result)
newChatId := result["gid"].(string)
sm.uiHandler.PrintText("got new Id " + newChatId)
newChat := Chat{}
newChat.Id = newChatId
newChat.Name = name
newChat.IsGroup = true
sm.db.chats[newChatId] = newChat
sm.uiHandler.SetChats(sm.db.GetChatIds())
}
sm.uiHandler.PrintError(err)
case "add":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
if !checkParam(command.Params, 1) {
sm.printCommandUsage("add", "[user-id[]")
return
}
wac := sm.getConnection()
var err error
_, err = wac.AddMember(groupId, command.Params)
if err == nil {
sm.uiHandler.PrintText("added new members for " + groupId)
}
sm.uiHandler.PrintError(err)
case "remove":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
if !checkParam(command.Params, 1) {
sm.printCommandUsage("remove", "[user-id[]")
return
}
wac := sm.getConnection()
var err error
_, err = wac.RemoveMember(groupId, command.Params)
if err == nil {
sm.uiHandler.PrintText("removed from " + groupId)
}
sm.uiHandler.PrintError(err)
case "removeadmin":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
if !checkParam(command.Params, 1) {
sm.printCommandUsage("removeadmin", "[user-id[]")
return
}
wac := sm.getConnection()
var err error
_, err = wac.RemoveAdmin(groupId, command.Params)
if err == nil {
sm.uiHandler.PrintText("removed admin for " + groupId)
}
sm.uiHandler.PrintError(err)
case "admin":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
if !checkParam(command.Params, 1) {
sm.printCommandUsage("admin", "[user-id[]")
return
}
wac := sm.getConnection()
var err error
_, err = wac.SetAdmin(groupId, command.Params)
if err == nil {
sm.uiHandler.PrintText("added admin for " + groupId)
}
sm.uiHandler.PrintError(err)
case "subject":
groupId := sm.currentReceiver
if strings.Index(groupId, GROUPSUFFIX) < 0 {
sm.uiHandler.PrintText("not a group")
return
}
if !checkParam(command.Params, 1) || groupId == "" {
sm.printCommandUsage("subject", "new-subject -> in group chat")
return
}
name := strings.Join(command.Params, " ")
wac := sm.getConnection()
var err error
_, err = wac.UpdateGroupSubject(name, groupId)
if err == nil {
sm.uiHandler.PrintText("updated subject for " + groupId)
}
newChat := sm.db.chats[groupId]
newChat.Name = name
sm.db.chats[groupId] = newChat
sm.uiHandler.SetChats(sm.db.GetChatIds())
sm.uiHandler.PrintError(err)
case "colorlist":
out := ""
for idx, _ := range tcell.ColorNames {
out = out + "[" + idx + "]" + idx + "[-]\n"
}
sm.uiHandler.PrintText(out)
}
}
// helper for built-in command help
func (sm *SessionManager) printCommandUsage(command string, usage string) {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] " + command + " " + usage)
}
// check if parameters for command are okay
func checkParam(arr []string, length int) bool {
if arr == nil || len(arr) < length {
return false
}
return true
}
// get all messages for one chat id
func (sm *SessionManager) getMessages(wid string) []Message {
msgs := sm.db.GetMessages(wid)
ids := []Message{}
for _, msg := range msgs {
ids = append(ids, sm.createMessage(&msg))
}
return ids
}
// create internal message from whatsapp message
// TODO: store these instead of generating each time
func (sm *SessionManager) createMessage(msg *whatsapp.TextMessage) Message {
newMsg := Message{}
newMsg.Id = msg.Info.Id
newMsg.ChatId = msg.Info.RemoteJid
newMsg.FromMe = msg.Info.FromMe
newMsg.Timestamp = msg.Info.Timestamp
newMsg.Text = msg.Text
newMsg.Forwarded = msg.ContextInfo.IsForwarded
if strings.Contains(msg.Info.RemoteJid, STATUSSUFFIX) {
newMsg.ContactId = msg.Info.SenderJid
newMsg.ContactName = sm.db.GetIdName(msg.Info.SenderJid)
newMsg.ContactShort = sm.db.GetIdShort(msg.Info.SenderJid)
} else if strings.Contains(msg.Info.RemoteJid, GROUPSUFFIX) {
newMsg.ContactId = msg.Info.SenderJid
newMsg.ContactName = sm.db.GetIdName(msg.Info.SenderJid)
newMsg.ContactShort = sm.db.GetIdShort(msg.Info.SenderJid)
} else {
newMsg.ContactId = msg.Info.RemoteJid
newMsg.ContactName = sm.db.GetIdName(msg.Info.RemoteJid)
newMsg.ContactShort = sm.db.GetIdShort(msg.Info.RemoteJid)
}
return newMsg
}
// load data for message specified by message id TODO: support types
func (sm *SessionManager) loadMessageData(wid string) ([]byte, error) {
if msg, ok := sm.db.otherMessages[wid]; ok {
switch v := (*msg).(type) {
default:
case whatsapp.ImageMessage:
return v.Download()
case whatsapp.DocumentMessage:
//return v.Download()
case whatsapp.AudioMessage:
//return v.Download()
case whatsapp.VideoMessage:
//return v.Download()
}
}
return []byte{}, errors.New("This is not an image message")
}
// attempts to download a messages attachments, returns path or error message
func (sm *SessionManager) downloadMessage(wid string, preview bool) (string, error) {
if msg, ok := sm.db.otherMessages[wid]; ok {
var fileName string = ""
if preview {
fileName += config.Config.General.PreviewPath
} else {
fileName += config.Config.General.DownloadPath
}
fileName += string(os.PathSeparator)
switch v := (*msg).(type) {
default:
case whatsapp.ImageMessage:
fileName += v.Info.Id
if exts, err := mime.ExtensionsByType(v.Type); err == nil {
if len(exts) > 0 {
fileName += exts[0]
}
}
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.DocumentMessage:
fileName += v.Info.Id
if exts, err := mime.ExtensionsByType(v.Type); err == nil {
if len(exts) > 0 {
fileName += exts[0]
}
}
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.AudioMessage:
fileName += v.Info.Id
if exts, err := mime.ExtensionsByType(v.Type); err == nil {
if len(exts) > 0 {
fileName += exts[0]
}
}
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.VideoMessage:
fileName += v.Info.Id
if exts, err := mime.ExtensionsByType(v.Type); err == nil {
if len(exts) > 0 {
fileName += exts[0]
}
}
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
}
}
return "", errors.New("No attachments found")
}
// sends text to whatsapp id
func (sm *SessionManager) sendText(wid string, text string) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: wid,
FromMe: true,
Timestamp: uint64(time.Now().Unix()),
},
Text: text,
}
sm.lastSent = time.Now()
newid, err := sm.getConnection().Send(msg)
msg.Info.Id = newid
if err != nil {
sm.uiHandler.PrintError(err)
} else {
sm.db.AddTextMessage(&msg)
if sm.currentReceiver == wid {
sm.uiHandler.NewMessage(sm.createMessage(&msg))
}
}
}
// handler struct for whatsapp callbacks
// HandleError implements the error handler interface for go-whatsapp
func (sm *SessionManager) HandleError(err error) {
sm.uiHandler.PrintError(err)
statusMsg := StatusMsg{false, err}
sm.StatusChannel <- statusMsg
return
}
// HandleTextMessage implements the text message handler interface for go-whatsapp
func (sm *SessionManager) HandleTextMessage(msg whatsapp.TextMessage) {
sm.TextChannel <- msg
}
// methods to convert messages to TextMessage
func (sm *SessionManager) HandleImageMessage(message whatsapp.ImageMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[IMAGE] " + message.Caption,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleDocumentMessage(message whatsapp.DocumentMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[DOCUMENT] " + message.Title,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleVideoMessage(message whatsapp.VideoMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[VIDEO] " + message.Caption,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleAudioMessage(message whatsapp.AudioMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[AUDIO]",
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
// add contact info to database (not needed, internal db of connection is used)
func (sm *SessionManager) HandleNewContact(contact whatsapp.Contact) {
// redundant, wac has contacts
sm.ContactChannel <- contact
}
// handle battery messages
func (sm *SessionManager) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
sm.BatteryChannel <- BatteryMsg{msg.Percentage, msg.Plugged, msg.Powersave}
}
func (sm *SessionManager) HandleContactList(contacts []whatsapp.Contact) {
for _, c := range contacts {
sm.ContactChannel <- c
}
}
func (sm *SessionManager) HandleChatList(chats []whatsapp.Chat) {
for _, c := range chats {
sm.ChatChannel <- c
}
}
func (sm *SessionManager) HandleJsonMessage(message string) {
//sm.uiHandler.PrintText(message)
}
// helper to save an attachment and open it if specified
func saveAttachment(data []byte, path string) (string, error) {
err := ioutil.WriteFile(path, data, 0644)
return path, err
}
// reads the session file from disk
func readSession() (whatsapp.Session, error) {
session := whatsapp.Session{}
file, err := os.Open(config.GetSessionFilePath())
if err != nil {
// load old session file, delete if found
file, err = os.Open(config.GetHomeDir() + ".whatscli.session")
if err != nil {
return session, err
} else {
os.Remove(config.GetHomeDir() + ".whatscli.session")
}
}
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&session)
if err != nil {
return session, err
}
return session, nil
}
// saves the session file to disk
func writeSession(session whatsapp.Session) error {
file, err := os.Create(config.GetSessionFilePath())
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(session)
if err != nil {
return err
}
return nil
}
// deletes the session file from disk
func removeSession() error {
return os.Remove(config.GetSessionFilePath())
}
// notify will send a notification via beeep if EnableNotification is true. If
// UseTerminalBell is true it will send a terminal bell instead.
func notify(title, message string) error {
if !config.Config.General.EnableNotifications {
return nil
} else if config.Config.General.UseTerminalBell {
_, err := fmt.Printf("\a")
return err
}
return beeep.Notify(title, message, "")
}