Compare commits

...

5 Commits

Author SHA1 Message Date
normen
04960123da fix freeze when downloading messages 2020-11-21 21:13:38 +01:00
normen
ab03aeb7a0 error output and README improvements 2020-11-20 14:40:01 +01:00
normen
f6860b56b8 secure connection handling with mutex 2020-11-20 14:31:17 +01:00
normen
6bea425367 make message database thread safe 2020-11-20 14:10:09 +01:00
normen
1b3379b613 learning GO - using its sugar 2020-11-20 13:24:15 +01:00
4 changed files with 74 additions and 49 deletions

View File

@@ -38,11 +38,11 @@ Always fresh, always up to date.
- Download a release - Download a release
- Put the binary in your PATH (optional) - Put the binary in your PATH (optional)
- Run with `whatscli` (or double-click) - Run with `whatscli` (or double-click)
- Scan the QR code with WhatsApp on your phone (maybe resize shell) - Scan the QR code with WhatsApp on your phone (resize shell or change font size to see whole code)
### Package Managers ### Package Managers
Some unofficial ways to install via package managers are supported but the installed version might be out of date. Some ways to install via package managers are supported but the installed version might be out of date.
#### MacOS (homebrew) #### MacOS (homebrew)

View File

@@ -19,7 +19,7 @@ type waMsg struct {
Text string Text string
} }
var VERSION string = "v0.6.8" var VERSION string = "v0.6.10"
var sendChannel chan waMsg var sendChannel chan waMsg
var textChannel chan whatsapp.TextMessage var textChannel chan whatsapp.TextMessage
@@ -552,7 +552,7 @@ type textHandler struct{}
// HandleError implements the error handler interface for go-whatsapp // HandleError implements the error handler interface for go-whatsapp
func (t textHandler) HandleError(err error) { func (t textHandler) HandleError(err error) {
// TODO : handle go routine here PrintText("[red]go-whatsapp reported an error:[-]")
PrintError(err) PrintError(err)
return return
} }

View File

@@ -3,15 +3,18 @@ package messages
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"github.com/rivo/tview"
"os" "os"
"sync"
"time" "time"
"github.com/rivo/tview"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
"github.com/normen/whatscli/qrcode" "github.com/normen/whatscli/qrcode"
) )
var textView *tview.TextView var textView *tview.TextView
var connMutex sync.Mutex
// TODO: remove this circular dependeny in favor of a better way // TODO: remove this circular dependeny in favor of a better way
func SetTextView(tv *tview.TextView) { func SetTextView(tv *tview.TextView) {
@@ -20,6 +23,8 @@ func SetTextView(tv *tview.TextView) {
// gets an existing connection or creates one // gets an existing connection or creates one
func GetConnection() *whatsapp.Conn { func GetConnection() *whatsapp.Conn {
connMutex.Lock()
defer connMutex.Unlock()
var wac *whatsapp.Conn var wac *whatsapp.Conn
if connection == nil { if connection == nil {
wacc, err := whatsapp.NewConn(5 * time.Second) wacc, err := whatsapp.NewConn(5 * time.Second)
@@ -44,8 +49,10 @@ func Login() error {
// LoginWithConnection logs in the user using a provided connection. It ries to see if a session already exists. If not, tries to create a // 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. // new one using qr scanned on the terminal.
func LoginWithConnection(wac *whatsapp.Conn) error { func LoginWithConnection(wac *whatsapp.Conn) error {
if connection != nil && connection.GetConnected() { connMutex.Lock()
connection.Disconnect() defer connMutex.Unlock()
if wac != nil && wac.GetConnected() {
wac.Disconnect()
} }
//load saved session //load saved session
session, err := readSession() session, err := readSession()
@@ -80,6 +87,8 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
// Logout logs out the user. // Logout logs out the user.
func Logout() error { func Logout() error {
connMutex.Lock()
defer connMutex.Unlock()
return removeSession() return removeSession()
} }

View File

@@ -6,6 +6,7 @@ import (
"os" "os"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
@@ -21,41 +22,46 @@ type MessageDatabase struct {
messagesById map[string]*whatsapp.TextMessage // text messages stored by message ID messagesById map[string]*whatsapp.TextMessage // text messages stored by message ID
latestMessage map[string]uint64 // last message from RemoteJid latestMessage map[string]uint64 // last message from RemoteJid
otherMessages map[string]*interface{} // other non-text messages, stored by ID otherMessages map[string]*interface{} // other non-text messages, stored by ID
mutex sync.Mutex
} }
// initialize the database // initialize the database
func (db *MessageDatabase) Init() { func (db *MessageDatabase) Init() {
//var this = *db //var this = *db
(*db).textMessages = make(map[string][]*whatsapp.TextMessage) db.textMessages = make(map[string][]*whatsapp.TextMessage)
(*db).messagesById = make(map[string]*whatsapp.TextMessage) db.messagesById = make(map[string]*whatsapp.TextMessage)
(*db).otherMessages = make(map[string]*interface{}) db.otherMessages = make(map[string]*interface{})
(*db).latestMessage = make(map[string]uint64) db.latestMessage = make(map[string]uint64)
} }
// add a text message to the database, stored by RemoteJid // add a text message to the database, stored by RemoteJid
func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool { func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool {
db.mutex.Lock()
defer db.mutex.Unlock()
//var this = *db //var this = *db
var didNew = false var didNew = false
var wid = msg.Info.RemoteJid var wid = msg.Info.RemoteJid
if (*db).textMessages[wid] == nil { if db.textMessages[wid] == nil {
var newArr = []*whatsapp.TextMessage{} var newArr = []*whatsapp.TextMessage{}
(*db).textMessages[wid] = newArr db.textMessages[wid] = newArr
(*db).latestMessage[wid] = msg.Info.Timestamp db.latestMessage[wid] = msg.Info.Timestamp
didNew = true didNew = true
} else if (*db).latestMessage[wid] < msg.Info.Timestamp { } else if db.latestMessage[wid] < msg.Info.Timestamp {
(*db).latestMessage[wid] = msg.Info.Timestamp db.latestMessage[wid] = msg.Info.Timestamp
didNew = true didNew = true
} }
(*db).textMessages[wid] = append((*db).textMessages[wid], msg) db.textMessages[wid] = append(db.textMessages[wid], msg)
(*db).messagesById[msg.Info.Id] = msg db.messagesById[msg.Info.Id] = msg
sort.Slice((*db).textMessages[wid], func(i, j int) bool { sort.Slice(db.textMessages[wid], func(i, j int) bool {
return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp return db.textMessages[wid][i].Info.Timestamp < db.textMessages[wid][j].Info.Timestamp
}) })
return didNew return didNew
} }
// add audio/video/image/doc message, stored by message id // add audio/video/image/doc message, stored by message id
func (db *MessageDatabase) AddOtherMessage(msg *interface{}) { func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
db.mutex.Lock()
defer db.mutex.Unlock()
var id = "" var id = ""
switch v := (*msg).(type) { switch v := (*msg).(type) {
default: default:
@@ -69,31 +75,35 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
id = v.Info.Id id = v.Info.Id
} }
if id != "" { if id != "" {
(*db).otherMessages[id] = msg db.otherMessages[id] = msg
} }
} }
// get an array of all chat ids // get an array of all chat ids
func (db *MessageDatabase) GetContactIds() []string { func (db *MessageDatabase) GetContactIds() []string {
db.mutex.Lock()
defer db.mutex.Unlock()
//var this = *db //var this = *db
keys := make([]string, len((*db).textMessages)) keys := make([]string, len(db.textMessages))
i := 0 i := 0
for k := range (*db).textMessages { for k := range db.textMessages {
keys[i] = k keys[i] = k
i++ i++
} }
sort.Slice(keys, func(i, j int) bool { sort.Slice(keys, func(i, j int) bool {
return (*db).latestMessage[keys[i]] > (*db).latestMessage[keys[j]] return db.latestMessage[keys[i]] > db.latestMessage[keys[j]]
}) })
return keys return keys
} }
func (db *MessageDatabase) GetMessageInfo(id string) string { func (db *MessageDatabase) GetMessageInfo(id string) string {
if _, ok := (*db).otherMessages[id]; ok { db.mutex.Lock()
defer db.mutex.Unlock()
if _, ok := db.otherMessages[id]; ok {
return "[yellow]OtherMessage[-]" return "[yellow]OtherMessage[-]"
} }
out := "" out := ""
if msg, ok := (*db).messagesById[id]; ok { if msg, ok := db.messagesById[id]; ok {
out += "[yellow]ID: " + msg.Info.Id + "[-]\n" out += "[yellow]ID: " + msg.Info.Id + "[-]\n"
out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n" out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n"
out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n" out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n"
@@ -106,10 +116,12 @@ func (db *MessageDatabase) GetMessageInfo(id string) string {
// get a string containing all messages for a chat by chat id // get a string containing all messages for a chat by chat id
func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) { func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
db.mutex.Lock()
defer db.mutex.Unlock()
//var this = *db //var this = *db
var out = "" var out = ""
var arr = []string{} var arr = []string{}
for _, element := range (*db).textMessages[wid] { for _, element := range db.textMessages[wid] {
out += GetTextMessageString(element) out += GetTextMessageString(element)
out += "\n" out += "\n"
arr = append(arr, element.Info.Id) arr = append(arr, element.Info.Id)
@@ -117,29 +129,11 @@ func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
return out, arr return out, arr
} }
// create a formatted string with regions based on message ID from a text message
func GetTextMessageString(msg *whatsapp.TextMessage) string {
out := ""
text := tview.Escape((*msg).Text)
tim := time.Unix(int64((*msg).Info.Timestamp), 0)
out += "[\""
out += (*msg).Info.Id
out += "\"]"
if (*msg).Info.FromMe { //msg from me
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [blue::b]Me: [-::-]" + text
} else if strings.Contains((*msg).Info.RemoteJid, GROUPSUFFIX) { // group msg
userId := (*msg).Info.SenderJid
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort(userId) + ": [-::-]" + text
} else { // message from others
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort((*msg).Info.RemoteJid) + ": [-::-]" + text
}
out += "[\"\"]"
return out
}
// load data for message specified by message id TODO: support types // load data for message specified by message id TODO: support types
func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) { func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
if msg, ok := (*db).otherMessages[wid]; ok { db.mutex.Lock()
defer db.mutex.Unlock()
if msg, ok := db.otherMessages[wid]; ok {
switch v := (*msg).(type) { switch v := (*msg).(type) {
default: default:
case whatsapp.ImageMessage: case whatsapp.ImageMessage:
@@ -157,7 +151,9 @@ func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
// attempts to download a messages attachments, returns path or error message // attempts to download a messages attachments, returns path or error message
func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) { func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) {
if msg, ok := (*db).otherMessages[wid]; ok { db.mutex.Lock()
defer db.mutex.Unlock()
if msg, ok := db.otherMessages[wid]; ok {
var fileName string = GetHomeDir() + "Downloads" + string(os.PathSeparator) var fileName string = GetHomeDir() + "Downloads" + string(os.PathSeparator)
switch v := (*msg).(type) { switch v := (*msg).(type) {
default: default:
@@ -210,6 +206,26 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
return "", errors.New("No attachments found") return "", errors.New("No attachments found")
} }
// create a formatted string with regions based on message ID from a text message
func GetTextMessageString(msg *whatsapp.TextMessage) string {
out := ""
text := tview.Escape(msg.Text)
tim := time.Unix(int64(msg.Info.Timestamp), 0)
out += "[\""
out += msg.Info.Id
out += "\"]"
if msg.Info.FromMe { //msg from me
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [blue::b]Me: [-::-]" + text
} else if strings.Contains(msg.Info.RemoteJid, GROUPSUFFIX) { // group msg
userId := msg.Info.SenderJid
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort(userId) + ": [-::-]" + text
} else { // message from others
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort(msg.Info.RemoteJid) + ": [-::-]" + text
}
out += "[\"\"]"
return out
}
// helper to save an attachment and open it if specified // helper to save an attachment and open it if specified
func saveAttachment(data []byte, path string, openIt bool) (string, error) { func saveAttachment(data []byte, path string, openIt bool) (string, error) {
err := ioutil.WriteFile(path, data, 0644) err := ioutil.WriteFile(path, data, 0644)