Compare commits

...

11 Commits

Author SHA1 Message Date
normen
4951ca24da help screen cleanups 2020-11-25 03:19:28 +01:00
normen
1cd6c25b02 fix preview and download path mixup 2020-11-25 03:11:38 +01:00
normen
d9a9e7f753 separate messages and interfaces to separate file 2020-11-25 02:12:57 +01:00
normen
5388a1b408 cleanup help 2020-11-25 01:38:06 +01:00
normen
7687af38e1 set file name when uploading 2020-11-24 23:16:29 +01:00
normen
9b26641a0c don't swallow error messages on upload/send 2020-11-24 22:53:36 +01:00
normen
e5ef667909 show help and set no receiver on contact root 2020-11-24 22:29:50 +01:00
normen
c39479f12b fix notifications for own messages 2020-11-24 22:28:24 +01:00
normen
f1109f6465 add /colorlist command 2020-11-24 21:05:12 +01:00
normen
127883701d fix version snafu in 0.9.1 release 2020-11-24 20:52:07 +01:00
normen
5cacc3c5ea bump version 2020-11-24 20:46:17 +01:00
3 changed files with 145 additions and 97 deletions

57
main.go
View File

@@ -4,7 +4,6 @@ import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
@@ -16,7 +15,7 @@ import (
"gitlab.com/tslocum/cbind"
)
var VERSION string = "v0.9.0"
var VERSION string = "v0.9.5"
var sndTxt string = ""
var currentReceiver string = ""
@@ -144,6 +143,7 @@ func MakeTree() *tview.TreeView {
treeView.SetChangedFunc(func(node *tview.TreeNode) {
reference := node.GetReference()
if reference == nil {
SetDisplayedContact("")
return // Selecting the root node does nothing.
}
children := node.GetChildren()
@@ -349,33 +349,39 @@ func PrintHelp() {
fmt.Fprintln(textView, "[::b]WhatsCLI "+VERSION+"[-]")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::u]Keys:[-::-]")
fmt.Fprintln(textView, "[::b] Up/Down[::-] = scroll history/contacts")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.SwitchPanels, "[::-] = switch input/contacts")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusMessages, "[::-] = focus message panel")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusContacts, "[::-] = focus contacts panel")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusInput, "[::-] = focus input")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::-]Message panel focused:[-::-]")
fmt.Fprintln(textView, "Global")
fmt.Fprintln(textView, "[::b] Up/Down[::-] = Scroll history/contacts")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.SwitchPanels, "[::-] = Switch input/contacts")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusMessages, "[::-] = Focus message panel")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::-]Message panel[-::-]")
fmt.Fprintln(textView, "[::b] Up/Down[::-] = select message")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageDownload, "[::-] = download attachment to", config.Config.General.DownloadPath)
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageOpen, "[::-] = download & open attachment in", config.Config.General.PreviewPath)
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageShow, "[::-] = download & show image using"+config.Config.General.ShowCommand, config.Config.General.PreviewPath+string(os.PathSeparator)+"filename.file")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageUrl, "[::-] = find URL in message and open it")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageRevoke, "[::-] = revoke message")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageInfo, "[::-] = info about message")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageDownload, "[::-] = Download attachment")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageOpen, "[::-] = Download & open attachment")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageShow, "[::-] = Download & show image using", config.Config.General.ShowCommand)
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageUrl, "[::-] = Find URL in message and open it")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageRevoke, "[::-] = Revoke message")
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageInfo, "[::-] = Info about message")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"backlog [::-]or[::b]", config.Config.Keymap.CommandBacklog, "[::-] = load next 10 older messages for current chat")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"connect [::-]or[::b]", config.Config.Keymap.CommandConnect, "[::-] = (re)connect in case the connection dropped")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"leave[::-] = leave group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"upload[::-] /path/to/file = upload any file as document to current chat")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendimage[::-] /path/to/file = send image as image message to current chat (also sendvideo/audio")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"disconnect[::-] = close the connection")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"logout[::-] = remove login data from computer (stays connected until app closes)")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"help [::-]or[::b]", config.Config.Keymap.CommandHelp, "[::-] = show this help")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"quit [::-]or[::b]", config.Config.Keymap.CommandQuit, "[::-] = exit app")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "Config file in \n-> ", config.GetConfigFilePath())
fmt.Fprintln(textView, "[-::-]Global[-::-]")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"connect [::-]or[::b]", config.Config.Keymap.CommandConnect, "[::-] = (Re)Connect to server")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"disconnect[::-] = Close the connection")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"logout[::-] = Remove login data from computer")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"quit [::-]or[::b]", config.Config.Keymap.CommandQuit, "[::-] = Exit app")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::-]Chat[-::-]")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"backlog [::-]or[::b]", config.Config.Keymap.CommandBacklog, "[::-] = load next 10 previous messages")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"upload[::-] /path/to/file = Upload any file as document")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendimage[::-] /path/to/file = Send image message")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendvideo[::-] /path/to/file = Send video message")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendaudio[::-] /path/to/file = Send audio message")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"leave[::-] = Leave group")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "Configuration:")
fmt.Fprintln(textView, " ->", config.GetConfigFilePath())
}
// called when text is entered by the user
@@ -548,6 +554,9 @@ func (u UiHandler) NewScreen(screen string, ids []string) {
textView.Clear()
textView.SetText(screen)
curRegions = ids
if screen == "" {
PrintHelp()
}
})
}

63
messages/messages.go Normal file
View File

@@ -0,0 +1,63 @@
//this package manages the messages
package messages
import "io"
// TODO: move these funcs/interface to channels
type UiMessageHandler interface {
NewMessage(string, string)
NewScreen(string, []string)
SetContacts([]string)
PrintError(error)
PrintText(string)
PrintFile(string)
SetStatus(SessionStatus)
OpenFile(string)
GetWriter() io.Writer
}
// data struct for current session status
type SessionStatus struct {
BatteryCharge int
BatteryLoading bool
BatteryPowersave bool
Connected bool
LastSeen string
}
// message struct for battery messages
type BatteryMsg struct {
charge int
loading bool
powersave bool
}
// message struct for status messages
type StatusMsg struct {
connected bool
err error
}
// message object for commands
type Command struct {
Name string
Params []string
}
// internal message representation to abstract from message lib
type Message struct {
id string
timestamp uint64
sourceId string
sourceName string
fromMe bool
}
// internal contact representation to abstract from message lib
type Contact struct {
id string
name string
}
const GROUPSUFFIX = "@g.us"
const CONTACTSUFFIX = "@s.whatsapp.net"

View File

@@ -4,14 +4,15 @@ import (
"encoding/gob"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"os"
"path/filepath"
"strings"
"time"
"github.com/gabriel-vasile/mimetype"
"github.com/gdamore/tcell/v2"
"github.com/gen2brain/beeep"
"github.com/rivo/tview"
"mvdan.cc/xurls/v2"
@@ -22,49 +23,6 @@ import (
)
// TODO: move message styling and ordering into UI, don't use strings
// move these funcs/interface to channels
type UiMessageHandler interface {
NewMessage(string, string)
NewScreen(string, []string)
SetContacts([]string)
PrintError(error)
PrintText(string)
PrintFile(string)
SetStatus(SessionStatus)
OpenFile(string)
GetWriter() io.Writer
}
// data struct for current session status
type SessionStatus struct {
BatteryCharge int
BatteryLoading bool
BatteryPowersave bool
Connected bool
LastSeen string
}
// message struct for battery messages
type BatteryMsg struct {
charge int
loading bool
powersave bool
}
// message struct for status messages
type StatusMsg struct {
connected bool
err error
}
// message object for commands
type Command struct {
Name string
Params []string
}
const GROUPSUFFIX = "@g.us"
const CONTACTSUFFIX = "@s.whatsapp.net"
// SessionManager deals with the connection and receives commands from the UI
// it updates the UI accordingly
@@ -115,7 +73,8 @@ func (sm *SessionManager) StartManager() error {
sm.uiHandler.NewScreen(screen, ids)
}
// notify if contact is in focus and we didn't send a message recently
if config.Config.General.EnableNotifications {
// TODO: move to UI (when UI has time in messages)
if config.Config.General.EnableNotifications && !msg.Info.FromMe {
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
if int64(msg.Info.Timestamp) > sm.lastSent.Unix()+config.Config.General.NotificationTimeout {
err := beeep.Notify(sm.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
@@ -126,7 +85,7 @@ func (sm *SessionManager) StartManager() error {
}
}
} else {
if config.Config.General.EnableNotifications {
if config.Config.General.EnableNotifications && !msg.Info.FromMe {
// notify if message is younger than 30 sec and not in focus
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
err := beeep.Notify(sm.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
@@ -244,6 +203,7 @@ func (sm *SessionManager) disconnect() error {
// logout logs out the user, deletes session file
func (ub *SessionManager) logout() error {
ub.getConnection().Disconnect()
return removeSession()
}
@@ -254,7 +214,6 @@ func (sm *SessionManager) execCommand(command Command) {
default:
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Unknown command: [-]" + cmd)
case "backlog":
//command
if sm.currentReceiver == "" {
return
}
@@ -265,8 +224,6 @@ func (sm *SessionManager) execCommand(command Command) {
go sm.getConnection().LoadChatMessages(sm.currentReceiver, count, firstMsg.Info.Id, firstMsg.Info.FromMe, false, sm)
}
}
//FullChatHistory(currentReceiver, 20, 100000, handler)
//messages.GetConnection().LoadFullChatHistory(currentReceiver, 20, 100000, handler)
case "login":
sm.uiHandler.PrintError(sm.login())
case "connect":
@@ -281,19 +238,19 @@ func (sm *SessionManager) execCommand(command Command) {
text := strings.Join(textParams, " ")
sm.sendText(command.Params[0], text)
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] send [user-id[] [message text[]")
sm.printCommandUsage("send", "[user-id[] [message text[]")
}
case "select":
if checkParam(command.Params, 1) {
sm.setCurrentReceiver(command.Params[0])
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] select [user-id[]")
sm.printCommandUsage("select", "[user-id[]")
}
case "info":
if checkParam(command.Params, 1) {
sm.uiHandler.PrintText(sm.db.GetMessageInfo(command.Params[0]))
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] info [message-id[]")
sm.printCommandUsage("info", "[message-id[]")
}
case "download":
if checkParam(command.Params, 1) {
@@ -303,7 +260,7 @@ func (sm *SessionManager) execCommand(command Command) {
sm.uiHandler.PrintText("[::d] -> " + path + "[::-]")
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] download [message-id[]")
sm.printCommandUsage("download", "[message-id[]")
}
case "open":
if checkParam(command.Params, 1) {
@@ -313,7 +270,7 @@ func (sm *SessionManager) execCommand(command Command) {
sm.uiHandler.PrintError(err)
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] open [message-id[]")
sm.printCommandUsage("open", "[message-id[]")
}
case "show":
if checkParam(command.Params, 1) {
@@ -323,7 +280,7 @@ func (sm *SessionManager) execCommand(command Command) {
sm.uiHandler.PrintError(err)
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] show [message-id[]")
sm.printCommandUsage("show", "[message-id[]")
}
case "url":
if checkParam(command.Params, 1) {
@@ -335,23 +292,25 @@ func (sm *SessionManager) execCommand(command Command) {
}
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] url [message-id[]")
sm.printCommandUsage("url", "[message-id[]")
}
case "upload":
if sm.currentReceiver == "" {
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 {
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(),
Content: file,
Type: mime.String(),
FileName: filepath.Base(file.Name()),
}
wac := sm.getConnection()
sm.lastSent = time.Now()
@@ -359,7 +318,7 @@ func (sm *SessionManager) execCommand(command Command) {
}
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] upload /path/to/file")
sm.printCommandUsage("upload", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendimage":
@@ -367,10 +326,12 @@ func (sm *SessionManager) execCommand(command Command) {
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 {
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,
@@ -384,7 +345,7 @@ func (sm *SessionManager) execCommand(command Command) {
}
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] sendimage /path/to/file")
sm.printCommandUsage("sendimage", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendvideo":
@@ -392,10 +353,12 @@ func (sm *SessionManager) execCommand(command Command) {
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 {
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,
@@ -409,7 +372,7 @@ func (sm *SessionManager) execCommand(command Command) {
}
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] sendvideo /path/to/file")
sm.printCommandUsage("sendvideo", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "sendaudio":
@@ -417,10 +380,12 @@ func (sm *SessionManager) execCommand(command Command) {
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 {
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,
@@ -434,7 +399,7 @@ func (sm *SessionManager) execCommand(command Command) {
}
}
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] sendaudio /path/to/file")
sm.printCommandUsage("sendaudio", "/path/to/file")
}
sm.uiHandler.PrintError(err)
case "revoke":
@@ -464,7 +429,7 @@ func (sm *SessionManager) execCommand(command Command) {
}
sm.uiHandler.PrintError(err)
} else {
sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] revoke [message-id[]")
sm.printCommandUsage("revoke", "[message-id[]")
}
case "leave":
groupId := sm.currentReceiver
@@ -478,9 +443,20 @@ func (sm *SessionManager) execCommand(command Command) {
sm.uiHandler.PrintText("left group " + groupId)
}
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 {
@@ -577,9 +553,9 @@ func (sm *SessionManager) downloadMessage(wid string, preview bool) (string, err
if msg, ok := sm.db.otherMessages[wid]; ok {
var fileName string = ""
if preview {
fileName += config.Config.General.DownloadPath
} else {
fileName += config.Config.General.PreviewPath
} else {
fileName += config.Config.General.DownloadPath
}
fileName += string(os.PathSeparator)
switch v := (*msg).(type) {