Compare commits

...

5 Commits

Author SHA1 Message Date
normen
6a832cbcf3 reduce data copying 2020-11-19 01:08:22 +01:00
normen
c2f3a012dc remove highlights when pressing <Esc> or o/d 2020-11-19 00:24:27 +01:00
normen
147feb90ec update README 2020-11-19 00:04:48 +01:00
normen
b39d4891e0 fix inverted logic for selecting messages 2020-11-19 00:02:21 +01:00
normen
19455b0baa add some code documentation 2020-11-18 23:58:28 +01:00
5 changed files with 68 additions and 20 deletions

View File

@@ -32,8 +32,9 @@ A command line interface for whatsapp, based on [go-whatsapp](https://github.com
Things that work.
- Connects through the Web App API without a browser
- Allows sending and receiving WhatsApp messages in a command line app
- Connects through the Web App API without browser
- Allows downloading and opening image/video/audio/document attachments
- Uses QR code for simple setup
- Binaries for Windows, Mac, Linux and RaspBerry Pi
@@ -43,7 +44,6 @@ This is a WIP. Heres some things you might expect to work that don't. Plus some
- Only shows existing chats
- Only fetches a few old messages
- No support for images, videos, documents etc.
- No incoming message notification / count
- No proper connection drop handling
- Not configurable at all

32
main.go
View File

@@ -16,7 +16,7 @@ type waMsg struct {
Text string
}
var VERSION string = "v0.6.0"
var VERSION string = "v0.6.2"
var sendChannel chan waMsg
var textChannel chan whatsapp.TextMessage
@@ -77,7 +77,11 @@ func main() {
}
if event.Key() == tcell.KeyTab {
app.SetFocus(textInput)
return event
return nil
}
if event.Key() == tcell.KeyEsc {
textView.Highlight("")
return nil
}
if curRegions == nil || len(curRegions) == 0 {
return event
@@ -122,6 +126,7 @@ func main() {
hls := textView.GetHighlights()
if len(hls) > 0 {
DownloadMessageId(hls[0], false)
textView.Highlight("")
}
return nil
}
@@ -129,6 +134,15 @@ func main() {
hls := textView.GetHighlights()
if len(hls) > 0 {
DownloadMessageId(hls[0], true)
textView.Highlight("")
}
return nil
}
if event.Rune() == 'i' {
hls := textView.GetHighlights()
if len(hls) > 0 {
fmt.Fprintf(textView, msgStore.GetMessageInfo(hls[0]))
textView.Highlight("")
}
return nil
}
@@ -336,6 +350,7 @@ func EnterCommand(key tcell.Key) {
textInput.SetText("")
}
// get the next message id to select (highlighted + offset)
func GetOffsetMsgId(curId string, offset int) string {
if curRegions == nil || len(curRegions) == 0 {
return ""
@@ -349,9 +364,9 @@ func GetOffsetMsgId(curId string, offset int) string {
}
}
if offset > 0 {
return curRegions[len(curRegions)-1]
} else {
return curRegions[0]
} else {
return curRegions[len(curRegions)-1]
}
}
@@ -403,7 +418,7 @@ func StartTextReceiver() error {
case msg := <-sendChannel:
SendText(msg.Wid, msg.Text)
case rcvd := <-textChannel:
if msgStore.AddTextMessage(rcvd) {
if msgStore.AddTextMessage(&rcvd) {
app.QueueUpdateDraw(LoadContacts)
}
case other := <-otherChannel:
@@ -433,11 +448,12 @@ func SendText(wid string, text string) {
if err != nil {
fmt.Fprintln(textView, "[red]error sending message: ", err, "[-]")
} else {
msgStore.AddTextMessage(msg)
msgStore.AddTextMessage(&msg)
PrintTextMessage(msg)
}
}
// initiates a download of a specific message attachment in a new go routine
func DownloadMessageId(id string, open bool) {
fmt.Fprintln(textView, "[::d]..attempt download of #", id, "[::-]")
go func() {
@@ -449,6 +465,7 @@ func DownloadMessageId(id string, open bool) {
}()
}
// notifies about a new message if its recent
func NotifyMsg(msg whatsapp.TextMessage) {
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
//fmt.Print("\a")
@@ -545,12 +562,13 @@ func (t textHandler) HandleAudioMessage(message whatsapp.AudioMessage) {
otherChannel <- message
}
// add contact info to database TODO: when are these sent??
// add contact info to database (not needed, internal db of connection is used)
func (t textHandler) HandleNewContact(contact whatsapp.Contact) {
// redundant, wac has contacts
//contactChannel <- contact
}
// handle battery messages
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
// app.QueueUpdate(func() {
// infoBar.SetText("🔋: " + string(msg.Percentage) + "%")

View File

@@ -12,6 +12,7 @@ import (
var contacts map[string]string
var connection *whatsapp.Conn
// loads custom contacts from disk
func LoadContacts() {
contacts = make(map[string]string)
file, err := os.Open(GetHomeDir() + ".whatscli.contacts")
@@ -26,6 +27,7 @@ func LoadContacts() {
}
}
// saves custom contacts to disk
func SaveContacts() {
file, err := os.Create(GetHomeDir() + ".whatscli.contacts")
if err != nil {
@@ -40,11 +42,13 @@ func SaveContacts() {
return
}
// sets a new name for a whatsapp id
func SetIdName(id string, name string) {
contacts[id] = name
SaveContacts()
}
// gets a pretty name for a whatsapp id
func GetIdName(id string) string {
if _, ok := contacts[id]; ok {
return contacts[id]
@@ -61,6 +65,7 @@ func GetIdName(id string) string {
return strings.TrimSuffix(id, CONTACTSUFFIX)
}
// gets a short name for a whatsapp id
func GetIdShort(id string) string {
if val, ok := connection.Store.Contacts[id]; ok {
if val.Short != "" {
@@ -77,6 +82,7 @@ func GetIdShort(id string) string {
return strings.TrimSuffix(id, CONTACTSUFFIX)
}
// gets the OS home dir with a path separator at the end
func GetHomeDir() string {
usr, err := user.Current()
if err != nil {

View File

@@ -11,13 +11,14 @@ import (
"github.com/normen/whatscli/qrcode"
)
// TODO: remove this circular dependeny in favor of a better way
var textView *tview.TextView
// TODO: remove this circular dependeny in favor of a better way
func SetTextView(tv *tview.TextView) {
textView = tv
}
// gets an existing connection or creates one
func GetConnection() *whatsapp.Conn {
var wac *whatsapp.Conn
if connection == nil {
@@ -82,6 +83,7 @@ func Logout() error {
return removeSession()
}
// reads the session file from disk
func readSession() (whatsapp.Session, error) {
session := whatsapp.Session{}
file, err := os.Open(GetHomeDir() + ".whatscli.session")
@@ -97,6 +99,7 @@ func readSession() (whatsapp.Session, error) {
return session, nil
}
// saves the session file to disk
func writeSession(session whatsapp.Session) error {
file, err := os.Create(GetHomeDir() + ".whatscli.session")
if err != nil {
@@ -111,6 +114,7 @@ func writeSession(session whatsapp.Session) error {
return nil
}
// deletes the session file from disk
func removeSession() error {
return os.Remove(GetHomeDir() + ".whatscli.session")
}

View File

@@ -17,26 +17,28 @@ const GROUPSUFFIX = "@g.us"
const CONTACTSUFFIX = "@s.whatsapp.net"
type MessageDatabase struct {
textMessages map[string][]whatsapp.TextMessage // text messages stored by RemoteJid
latestMessage map[string]uint64 // last message from RemoteJid
otherMessages map[string]interface{} // other non-text messages, stored by ID
textMessages map[string][]*whatsapp.TextMessage // text messages stored by RemoteJid
messagesById map[string]*whatsapp.TextMessage // text messages stored by message ID
latestMessage map[string]uint64 // last message from RemoteJid
otherMessages map[string]*interface{} // other non-text messages, stored by ID
}
// initialize the database
func (db *MessageDatabase) Init() {
//var this = *db
(*db).textMessages = make(map[string][]whatsapp.TextMessage)
(*db).otherMessages = make(map[string]interface{})
(*db).textMessages = make(map[string][]*whatsapp.TextMessage)
(*db).messagesById = make(map[string]*whatsapp.TextMessage)
(*db).otherMessages = make(map[string]*interface{})
(*db).latestMessage = make(map[string]uint64)
}
// 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 {
//var this = *db
var didNew = false
var wid = msg.Info.RemoteJid
if (*db).textMessages[wid] == nil {
var newArr = []whatsapp.TextMessage{}
var newArr = []*whatsapp.TextMessage{}
(*db).textMessages[wid] = newArr
(*db).latestMessage[wid] = msg.Info.Timestamp
didNew = true
@@ -45,6 +47,7 @@ func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool {
didNew = true
}
(*db).textMessages[wid] = append((*db).textMessages[wid], msg)
(*db).messagesById[msg.Info.Id] = msg
sort.Slice((*db).textMessages[wid], func(i, j int) bool {
return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp
})
@@ -66,7 +69,7 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
id = v.Info.Id
}
if id != "" {
(*db).otherMessages[id] = (*msg)
(*db).otherMessages[id] = msg
}
}
@@ -85,13 +88,29 @@ func (db *MessageDatabase) GetContactIds() []string {
return keys
}
func (db *MessageDatabase) GetMessageInfo(id string) string {
if _, ok := (*db).otherMessages[id]; ok {
return "[yellow]OtherMessage[-]"
}
out := ""
if msg, ok := (*db).messagesById[id]; ok {
out += "[yellow]ID: " + msg.Info.Id + "[-]\n"
out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n"
out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n"
out += "[yellow]SenderJid: " + msg.Info.SenderJid + "[-]\n"
out += "[yellow]Participant: " + msg.ContextInfo.Participant + "[-]\n"
out += "[yellow]QuotedMessageID: " + msg.ContextInfo.QuotedMessageID + "[-]\n"
}
return out
}
// get a string containing all messages for a chat by chat id
func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
//var this = *db
var out = ""
var arr = []string{}
for _, element := range (*db).textMessages[wid] {
out += GetTextMessageString(&element)
out += GetTextMessageString(element)
out += "\n"
arr = append(arr, element.Info.Id)
}
@@ -122,7 +141,7 @@ func GetTextMessageString(msg *whatsapp.TextMessage) string {
func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) {
if msg, ok := (*db).otherMessages[wid]; ok {
var fileName string = ""
switch v := msg.(type) {
switch v := (*msg).(type) {
default:
case whatsapp.ImageMessage:
if data, err := v.Download(); err == nil {
@@ -161,6 +180,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
return "", errors.New("No attachments found")
}
// helper to save an attachment and open it if specified
func saveAttachment(data []byte, fileName string, openIt bool) error {
path := GetHomeDir() + "Downloads" + string(os.PathSeparator) + fileName
err := ioutil.WriteFile(path, data, 0644)