Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86af0d82a4 | ||
|
|
b94129fb0e | ||
|
|
59a843bb8d | ||
|
|
a86aa3eec3 | ||
|
|
a20b4e3592 | ||
|
|
9e62295188 | ||
|
|
2e891d05ca | ||
|
|
bfbec54de3 | ||
|
|
c677bce14e | ||
|
|
6f30efeebe | ||
|
|
14a0e74b25 |
@@ -34,7 +34,7 @@ type Keymap struct {
|
||||
SwitchPanels string
|
||||
FocusMessages string
|
||||
FocusInput string
|
||||
FocusContacts string
|
||||
FocusChats string
|
||||
CommandBacklog string
|
||||
CommandConnect string
|
||||
CommandQuit string
|
||||
@@ -48,7 +48,7 @@ type Keymap struct {
|
||||
}
|
||||
|
||||
type Ui struct {
|
||||
ContactSidebarWidth int
|
||||
ChatSidebarWidth int
|
||||
}
|
||||
|
||||
type Colors struct {
|
||||
@@ -79,7 +79,7 @@ var Config = IniFile{
|
||||
SwitchPanels: "Tab",
|
||||
FocusMessages: "Ctrl+w",
|
||||
FocusInput: "Ctrl+Space",
|
||||
FocusContacts: "Ctrl+e",
|
||||
FocusChats: "Ctrl+e",
|
||||
CommandBacklog: "Ctrl+b",
|
||||
CommandConnect: "Ctrl+r",
|
||||
CommandQuit: "Ctrl+q",
|
||||
@@ -92,7 +92,7 @@ var Config = IniFile{
|
||||
MessageShow: "s",
|
||||
},
|
||||
&Ui{
|
||||
ContactSidebarWidth: 30,
|
||||
ChatSidebarWidth: 30,
|
||||
},
|
||||
&Colors{
|
||||
Background: "black",
|
||||
@@ -160,13 +160,6 @@ func GetSessionFilePath() string {
|
||||
return GetHomeDir() + ".whatscli.session"
|
||||
}
|
||||
|
||||
func GetContactsFilePath() string {
|
||||
if sessionFilePath, err := xdg.ConfigFile("whatscli/contacts"); err == nil {
|
||||
return sessionFilePath
|
||||
}
|
||||
return GetHomeDir() + ".whatscli.contacts"
|
||||
}
|
||||
|
||||
// gets the OS home dir with a path separator at the end
|
||||
func GetHomeDir() string {
|
||||
usr, err := user.Current()
|
||||
|
||||
4
go.mod
4
go.mod
@@ -17,8 +17,8 @@ require (
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
gitlab.com/tslocum/cbind v0.1.4
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
|
||||
golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -97,6 +97,8 @@ golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -124,6 +126,8 @@ golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 h1:WFCmm2Hi9I2gYf1kv7LQ8ajKA5x9heC2v9xuUKwvf68=
|
||||
golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
||||
133
main.go
133
main.go
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/normen/whatscli/config"
|
||||
@@ -15,11 +16,11 @@ import (
|
||||
"gitlab.com/tslocum/cbind"
|
||||
)
|
||||
|
||||
var VERSION string = "v0.9.5"
|
||||
var VERSION string = "v0.9.7"
|
||||
|
||||
var sndTxt string = ""
|
||||
var currentReceiver string = ""
|
||||
var curRegions []string
|
||||
var currentReceiver messages.Chat = messages.Chat{}
|
||||
var curRegions []messages.Message
|
||||
|
||||
var textView *tview.TextView
|
||||
var treeView *tview.TreeView
|
||||
@@ -27,7 +28,7 @@ var textInput *tview.InputField
|
||||
var topBar *tview.TextView
|
||||
var infoBar *tview.TextView
|
||||
|
||||
var contactRoot *tview.TreeNode
|
||||
var chatRoot *tview.TreeNode
|
||||
var app *tview.Application
|
||||
|
||||
var sessionManager *messages.SessionManager
|
||||
@@ -44,7 +45,7 @@ func main() {
|
||||
|
||||
app = tview.NewApplication()
|
||||
|
||||
sideBarWidth := config.Config.Ui.ContactSidebarWidth
|
||||
sideBarWidth := config.Config.Ui.ChatSidebarWidth
|
||||
gridLayout := tview.NewGrid()
|
||||
gridLayout.SetRows(1, 0, 1)
|
||||
gridLayout.SetColumns(sideBarWidth, 0, sideBarWidth)
|
||||
@@ -120,37 +121,35 @@ func main() {
|
||||
app.SetRoot(gridLayout, true)
|
||||
app.EnableMouse(true)
|
||||
app.SetFocus(textInput)
|
||||
go func() {
|
||||
if err := sessionManager.StartManager(); err != nil {
|
||||
PrintError(err)
|
||||
}
|
||||
}()
|
||||
if err := sessionManager.StartManager(); err != nil {
|
||||
PrintError(err)
|
||||
}
|
||||
LoadShortcuts()
|
||||
app.Run()
|
||||
}
|
||||
|
||||
// creates the TreeView for contacts
|
||||
// creates the TreeView for chats
|
||||
func MakeTree() *tview.TreeView {
|
||||
rootDir := "Contacts"
|
||||
contactRoot = tview.NewTreeNode(rootDir).
|
||||
rootDir := "Chats"
|
||||
chatRoot = tview.NewTreeNode(rootDir).
|
||||
SetColor(tcell.ColorNames[config.Config.Colors.ListHeader])
|
||||
treeView = tview.NewTreeView().
|
||||
SetRoot(contactRoot).
|
||||
SetCurrentNode(contactRoot)
|
||||
SetRoot(chatRoot).
|
||||
SetCurrentNode(chatRoot)
|
||||
treeView.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
|
||||
|
||||
// If a contact was selected, open it.
|
||||
// If a chat was selected, open it.
|
||||
treeView.SetChangedFunc(func(node *tview.TreeNode) {
|
||||
reference := node.GetReference()
|
||||
if reference == nil {
|
||||
SetDisplayedContact("")
|
||||
SetDisplayedChat(messages.Chat{"", false, ""})
|
||||
return // Selecting the root node does nothing.
|
||||
}
|
||||
children := node.GetChildren()
|
||||
if len(children) == 0 {
|
||||
// Load and show files in this directory.
|
||||
recv := reference.(string)
|
||||
SetDisplayedContact(recv)
|
||||
recv := reference.(messages.Chat)
|
||||
SetDisplayedChat(recv)
|
||||
} else {
|
||||
// Collapse if visible, expand if collapsed.
|
||||
node.SetExpanded(!node.IsExpanded())
|
||||
@@ -163,7 +162,7 @@ func handleFocusMessage(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if !textView.HasFocus() {
|
||||
app.SetFocus(textView)
|
||||
if curRegions != nil && len(curRegions) > 0 {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.Highlight(curRegions[len(curRegions)-1].Id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -236,7 +235,7 @@ func handleMessagesUp(ev *tcell.EventKey) *tcell.EventKey {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.Highlight(curRegions[len(curRegions)-1].Id)
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
@@ -253,7 +252,7 @@ func handleMessagesDown(ev *tcell.EventKey) *tcell.EventKey {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[0])
|
||||
textView.Highlight(curRegions[0].Id)
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
@@ -263,7 +262,7 @@ func handleMessagesLast(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.Highlight(curRegions[len(curRegions)-1].Id)
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
@@ -272,7 +271,7 @@ func handleMessagesFirst(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
textView.Highlight(curRegions[0])
|
||||
textView.Highlight(curRegions[0].Id)
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
@@ -295,7 +294,7 @@ func LoadShortcuts() {
|
||||
if err := keyBindings.Set(config.Config.Keymap.FocusInput, handleFocusInput); err != nil {
|
||||
PrintErrorMsg("focus_input:", err)
|
||||
}
|
||||
if err := keyBindings.Set(config.Config.Keymap.FocusContacts, handleFocusContacts); err != nil {
|
||||
if err := keyBindings.Set(config.Config.Keymap.FocusChats, handleFocusContacts); err != nil {
|
||||
PrintErrorMsg("focus_contacts:", err)
|
||||
}
|
||||
if err := keyBindings.Set(config.Config.Keymap.SwitchPanels, handleSwitchPanels); err != nil {
|
||||
@@ -351,8 +350,8 @@ func PrintHelp() {
|
||||
fmt.Fprintln(textView, "[-::u]Keys:[-::-]")
|
||||
fmt.Fprintln(textView, "")
|
||||
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] Up/Down[::-] = Scroll history/chats")
|
||||
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.SwitchPanels, "[::-] = Switch input/chats")
|
||||
fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusMessages, "[::-] = Focus message panel")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::-]Message panel[-::-]")
|
||||
@@ -422,7 +421,7 @@ func EnterCommand(key tcell.Key) {
|
||||
// no command, send as message
|
||||
msg := messages.Command{
|
||||
Name: "send",
|
||||
Params: []string{currentReceiver, sndTxt},
|
||||
Params: []string{currentReceiver.Id, sndTxt},
|
||||
}
|
||||
sessionManager.CommandChannel <- msg
|
||||
textInput.SetText("")
|
||||
@@ -434,17 +433,17 @@ func GetOffsetMsgId(curId string, offset int) string {
|
||||
return ""
|
||||
}
|
||||
for idx, val := range curRegions {
|
||||
if val == curId {
|
||||
if val.Id == curId {
|
||||
arrPos := idx + offset
|
||||
if len(curRegions) > arrPos && arrPos >= 0 {
|
||||
return curRegions[arrPos]
|
||||
return curRegions[arrPos].Id
|
||||
}
|
||||
}
|
||||
}
|
||||
if offset > 0 {
|
||||
return curRegions[0]
|
||||
return curRegions[0].Id
|
||||
} else {
|
||||
return curRegions[len(curRegions)-1]
|
||||
return curRegions[len(curRegions)-1].Id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,50 +529,86 @@ func UpdateStatusBar(statusInfo messages.SessionStatus) {
|
||||
//infoBar.SetText("🔋: ??%")
|
||||
}
|
||||
|
||||
// sets the current contact, loads text from storage to TextView
|
||||
func SetDisplayedContact(wid string) {
|
||||
// sets the current chat, loads text from storage to TextView
|
||||
func SetDisplayedChat(wid messages.Chat) {
|
||||
//TODO: how to get chat to set
|
||||
currentReceiver = wid
|
||||
textView.Clear()
|
||||
textView.SetTitle(sessionManager.GetIdName(wid))
|
||||
sessionManager.CommandChannel <- messages.Command{"select", []string{currentReceiver}}
|
||||
textView.SetTitle(wid.Name)
|
||||
sessionManager.CommandChannel <- messages.Command{"select", []string{currentReceiver.Id}}
|
||||
}
|
||||
|
||||
// get a string representation of all messages for chat
|
||||
func getMessagesString(msgs []messages.Message) string {
|
||||
out := ""
|
||||
for _, msg := range msgs {
|
||||
out += getTextMessageString(&msg)
|
||||
out += "\n"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// create a formatted string with regions based on message ID from a text message
|
||||
func getTextMessageString(msg *messages.Message) string {
|
||||
colorMe := config.Config.Colors.ChatMe
|
||||
colorContact := config.Config.Colors.ChatContact
|
||||
out := ""
|
||||
text := tview.Escape(msg.Text)
|
||||
tim := time.Unix(int64(msg.Timestamp), 0)
|
||||
time := tim.Format("02-01-06 15:04:05")
|
||||
out += "[\""
|
||||
out += msg.Id
|
||||
out += "\"]"
|
||||
if msg.FromMe { //msg from me
|
||||
out += "[-::d](" + time + ") [" + colorMe + "::b]Me: [-::-]" + text
|
||||
} else { // message from others
|
||||
out += "[-::d](" + time + ") [" + colorContact + "::b]" + msg.ContactShort + ": [-::-]" + text
|
||||
}
|
||||
out += "[\"\"]"
|
||||
return out
|
||||
}
|
||||
|
||||
type UiHandler struct{}
|
||||
|
||||
func (u UiHandler) NewMessage(msg string, id string) {
|
||||
func (u UiHandler) NewMessage(msg messages.Message) {
|
||||
//TODO: its stupid to "go" this as its supposed to run
|
||||
//on the ui thread anyway. But QueueUpdate blocks...?
|
||||
go app.QueueUpdateDraw(func() {
|
||||
curRegions = append(curRegions, id)
|
||||
PrintText(msg)
|
||||
curRegions = append(curRegions, msg)
|
||||
PrintText(getTextMessageString(&msg))
|
||||
})
|
||||
}
|
||||
|
||||
func (u UiHandler) NewScreen(screen string, ids []string) {
|
||||
func (u UiHandler) NewScreen(msgs []messages.Message) {
|
||||
go app.QueueUpdateDraw(func() {
|
||||
textView.Clear()
|
||||
screen := getMessagesString(msgs)
|
||||
textView.SetText(screen)
|
||||
curRegions = ids
|
||||
curRegions = msgs
|
||||
if screen == "" {
|
||||
PrintHelp()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// loads the contact data from storage to the TreeView
|
||||
func (u UiHandler) SetContacts(ids []string) {
|
||||
// loads the chat data from storage to the TreeView
|
||||
func (u UiHandler) SetChats(ids []messages.Chat) {
|
||||
go app.QueueUpdateDraw(func() {
|
||||
contactRoot.ClearChildren()
|
||||
chatRoot.ClearChildren()
|
||||
for _, element := range ids {
|
||||
node := tview.NewTreeNode(sessionManager.GetIdName(element)).
|
||||
name := element.Name
|
||||
if name == "" {
|
||||
name = strings.TrimSuffix(strings.TrimSuffix(element.Id, messages.GROUPSUFFIX), messages.CONTACTSUFFIX)
|
||||
}
|
||||
node := tview.NewTreeNode(name).
|
||||
SetReference(element).
|
||||
SetSelectable(true)
|
||||
if strings.Count(element, messages.CONTACTSUFFIX) > 0 {
|
||||
node.SetColor(tcell.ColorNames[config.Config.Colors.ListContact])
|
||||
} else {
|
||||
if element.IsGroup {
|
||||
node.SetColor(tcell.ColorNames[config.Config.Colors.ListGroup])
|
||||
} else {
|
||||
node.SetColor(tcell.ColorNames[config.Config.Colors.ListContact])
|
||||
}
|
||||
contactRoot.AddChild(node)
|
||||
chatRoot.AddChild(node)
|
||||
if element == currentReceiver {
|
||||
treeView.SetCurrentNode(node)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import "io"
|
||||
|
||||
// TODO: move these funcs/interface to channels
|
||||
type UiMessageHandler interface {
|
||||
NewMessage(string, string)
|
||||
NewScreen(string, []string)
|
||||
SetContacts([]string)
|
||||
NewMessage(Message)
|
||||
NewScreen([]Message)
|
||||
SetChats([]Chat)
|
||||
PrintError(error)
|
||||
PrintText(string)
|
||||
PrintFile(string)
|
||||
@@ -46,18 +46,29 @@ type Command struct {
|
||||
|
||||
// internal message representation to abstract from message lib
|
||||
type Message struct {
|
||||
id string
|
||||
timestamp uint64
|
||||
sourceId string
|
||||
sourceName string
|
||||
fromMe bool
|
||||
Id string
|
||||
ChatId string // the source of the message (group id or contact id)
|
||||
ContactId string
|
||||
ContactName string
|
||||
ContactShort string
|
||||
Timestamp uint64
|
||||
FromMe bool
|
||||
Text string
|
||||
}
|
||||
|
||||
// internal contact representation to abstract from message lib
|
||||
type Chat struct {
|
||||
Id string
|
||||
IsGroup bool
|
||||
Name string
|
||||
}
|
||||
|
||||
type Contact struct {
|
||||
id string
|
||||
name string
|
||||
Id string
|
||||
Name string
|
||||
Short string
|
||||
}
|
||||
|
||||
const GROUPSUFFIX = "@g.us"
|
||||
const CONTACTSUFFIX = "@s.whatsapp.net"
|
||||
const STATUSSUFFIX = "status@broadcast"
|
||||
|
||||
@@ -22,62 +22,73 @@ import (
|
||||
"github.com/normen/whatscli/qrcode"
|
||||
)
|
||||
|
||||
// TODO: move message styling and ordering into UI, don't use strings
|
||||
|
||||
// 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 contact for message handling
|
||||
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 = &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 thread
|
||||
// TODO: can't be stopped, can only be called once!
|
||||
// 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 {
|
||||
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.getTextMessageString(&msg), msg.Info.Id)
|
||||
sm.uiHandler.NewMessage(sm.createMessage(&msg))
|
||||
} else {
|
||||
screen, ids := sm.getMessagesString(sm.currentReceiver)
|
||||
sm.uiHandler.NewScreen(screen, ids)
|
||||
screen := sm.getMessages(sm.currentReceiver)
|
||||
sm.uiHandler.NewScreen(screen)
|
||||
}
|
||||
// notify if contact is in focus and we didn't send a message recently
|
||||
// notify if chat is in focus and we didn't send a message recently
|
||||
// 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, "")
|
||||
err := beeep.Notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
|
||||
if err != nil {
|
||||
sm.uiHandler.PrintError(err)
|
||||
}
|
||||
@@ -88,16 +99,40 @@ func (sm *SessionManager) StartManager() error {
|
||||
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, "")
|
||||
err := beeep.Notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
|
||||
if err != nil {
|
||||
sm.uiHandler.PrintError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sm.uiHandler.SetContacts(sm.db.GetContactIds())
|
||||
chats := sm.db.GetChatIds()
|
||||
sm.uiHandler.SetChats(chats)
|
||||
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)
|
||||
case c := <-sm.ChatChannel:
|
||||
if c.IsMarkedSpam == "false" {
|
||||
isGroup := strings.Contains(c.Jid, GROUPSUFFIX)
|
||||
chat := Chat{
|
||||
c.Jid,
|
||||
isGroup,
|
||||
c.Name,
|
||||
}
|
||||
sm.db.AddChat(chat)
|
||||
}
|
||||
case command := <-sm.CommandChannel:
|
||||
sm.execCommand(command)
|
||||
case batteryMsg := <-sm.BatteryChannel:
|
||||
@@ -121,11 +156,11 @@ func (sm *SessionManager) StartManager() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set the currently selected contact
|
||||
// set the currently selected chat
|
||||
func (sm *SessionManager) setCurrentReceiver(id string) {
|
||||
sm.currentReceiver = id
|
||||
screen, ids := sm.getMessagesString(id)
|
||||
sm.uiHandler.NewScreen(screen, ids)
|
||||
screen := sm.getMessages(id)
|
||||
sm.uiHandler.NewScreen(screen)
|
||||
}
|
||||
|
||||
// gets an existing connection or creates one
|
||||
@@ -223,6 +258,8 @@ func (sm *SessionManager) execCommand(command Command) {
|
||||
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)
|
||||
}
|
||||
case "login":
|
||||
sm.uiHandler.PrintError(sm.login())
|
||||
@@ -465,69 +502,38 @@ func checkParam(arr []string, length int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// gets a pretty name for a whatsapp id
|
||||
func (sm *SessionManager) GetIdName(id string) string {
|
||||
if val, ok := sm.getConnection().Store.Contacts[id]; ok {
|
||||
if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Notify != "" {
|
||||
return val.Notify
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(id, CONTACTSUFFIX)
|
||||
}
|
||||
|
||||
// gets a short name for a whatsapp id
|
||||
func (sm *SessionManager) GetIdShort(id string) string {
|
||||
if val, ok := sm.getConnection().Store.Contacts[id]; ok {
|
||||
if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Notify != "" {
|
||||
return val.Notify
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(id, CONTACTSUFFIX)
|
||||
}
|
||||
|
||||
// get a string representation of all messages for contact
|
||||
func (sm *SessionManager) getMessagesString(wid string) (string, []string) {
|
||||
out := ""
|
||||
ids := []string{}
|
||||
// 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 {
|
||||
out += sm.getTextMessageString(&msg)
|
||||
out += "\n"
|
||||
ids = append(ids, msg.Info.Id)
|
||||
ids = append(ids, sm.createMessage(&msg))
|
||||
}
|
||||
return out, ids
|
||||
return ids
|
||||
}
|
||||
|
||||
// create a formatted string with regions based on message ID from a text message
|
||||
// TODO: move message styling into UI
|
||||
func (sm *SessionManager) getTextMessageString(msg *whatsapp.TextMessage) string {
|
||||
colorMe := config.Config.Colors.ChatMe
|
||||
colorContact := config.Config.Colors.ChatContact
|
||||
out := ""
|
||||
text := tview.Escape(msg.Text)
|
||||
tim := time.Unix(int64(msg.Info.Timestamp), 0)
|
||||
time := tim.Format("02-01-06 15:04:05")
|
||||
out += "[\""
|
||||
out += msg.Info.Id
|
||||
out += "\"]"
|
||||
if msg.Info.FromMe { //msg from me
|
||||
out += "[-::d](" + time + ") [" + colorMe + "::b]Me: [-::-]" + text
|
||||
} else if strings.Contains(msg.Info.RemoteJid, GROUPSUFFIX) { // group msg
|
||||
userId := msg.Info.SenderJid
|
||||
out += "[-::d](" + time + ") [" + colorContact + "::b]" + sm.GetIdShort(userId) + ": [-::-]" + text
|
||||
} else { // message from others
|
||||
out += "[-::d](" + time + ") [" + colorContact + "::b]" + sm.GetIdShort(msg.Info.RemoteJid) + ": [-::-]" + text
|
||||
// create internal message from whatsapp message
|
||||
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
|
||||
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)
|
||||
}
|
||||
out += "[\"\"]"
|
||||
return out
|
||||
return newMsg
|
||||
}
|
||||
|
||||
// load data for message specified by message id TODO: support types
|
||||
@@ -639,7 +645,7 @@ func (sm *SessionManager) sendText(wid string, text string) {
|
||||
} else {
|
||||
sm.db.AddTextMessage(&msg)
|
||||
if sm.currentReceiver == wid {
|
||||
sm.uiHandler.NewMessage(sm.getTextMessageString(&msg), msg.Info.Id)
|
||||
sm.uiHandler.NewMessage(sm.createMessage(&msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -724,7 +730,7 @@ func (sm *SessionManager) HandleAudioMessage(message whatsapp.AudioMessage) {
|
||||
// add contact info to database (not needed, internal db of connection is used)
|
||||
func (sm *SessionManager) HandleNewContact(contact whatsapp.Contact) {
|
||||
// redundant, wac has contacts
|
||||
//contactChannel <- contact
|
||||
sm.ContactChannel <- contact
|
||||
}
|
||||
|
||||
// handle battery messages
|
||||
@@ -732,6 +738,18 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// helper to save an attachment and open it if specified
|
||||
func saveAttachment(data []byte, path string) (string, error) {
|
||||
err := ioutil.WriteFile(path, data, 0644)
|
||||
|
||||
@@ -2,6 +2,7 @@ package messages
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
)
|
||||
@@ -11,6 +12,8 @@ type MessageDatabase struct {
|
||||
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
|
||||
contacts map[string]Contact
|
||||
chats map[string]Chat
|
||||
}
|
||||
|
||||
// initialize the database
|
||||
@@ -20,6 +23,8 @@ func (db *MessageDatabase) Init() {
|
||||
db.messagesById = make(map[string]*whatsapp.TextMessage)
|
||||
db.otherMessages = make(map[string]*interface{})
|
||||
db.latestMessage = make(map[string]uint64)
|
||||
db.contacts = make(map[string]Contact)
|
||||
db.chats = make(map[string]Chat)
|
||||
}
|
||||
|
||||
// add a text message to the database, stored by RemoteJid
|
||||
@@ -66,21 +71,53 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) AddContact(contact Contact) {
|
||||
db.contacts[contact.Id] = contact
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) AddChat(chat Chat) {
|
||||
db.chats[chat.Id] = chat
|
||||
}
|
||||
|
||||
// get an array of all chat ids
|
||||
func (db *MessageDatabase) GetContactIds() []string {
|
||||
//var this = *db
|
||||
keys := make([]string, len(db.textMessages))
|
||||
func (db *MessageDatabase) GetChatIds() []Chat {
|
||||
keys := make([]Chat, len(db.chats))
|
||||
i := 0
|
||||
for k := range db.textMessages {
|
||||
for _, k := range db.chats {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return db.latestMessage[keys[i]] > db.latestMessage[keys[j]]
|
||||
return db.latestMessage[keys[i].Id] > db.latestMessage[keys[j].Id]
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// gets a pretty name for a whatsapp id
|
||||
func (sm *MessageDatabase) GetIdName(id string) string {
|
||||
if val, ok := sm.contacts[id]; ok {
|
||||
if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Short != "" {
|
||||
return val.Short
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
|
||||
}
|
||||
|
||||
// gets a short name for a whatsapp id
|
||||
func (sm *MessageDatabase) GetIdShort(id string) string {
|
||||
if val, ok := sm.contacts[id]; ok {
|
||||
//TODO val.notify from whatsapp??
|
||||
if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Name != "" {
|
||||
return val.Name
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) GetMessageInfo(id string) string {
|
||||
if _, ok := db.otherMessages[id]; ok {
|
||||
return "[yellow]OtherMessage[-]"
|
||||
|
||||
Reference in New Issue
Block a user