Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80825b0dff | ||
|
|
42daf1a9f7 | ||
|
|
04960123da | ||
|
|
ab03aeb7a0 | ||
|
|
f6860b56b8 | ||
|
|
6bea425367 | ||
|
|
1b3379b613 |
11
README.md
11
README.md
@@ -23,8 +23,6 @@ This is a WIP and mainly meant for my personal use. Heres some things you might
|
||||
- No incoming message notification / count
|
||||
- No proper connection drop handling
|
||||
- No uploading of images/video/audio/data
|
||||
- Not configurable at all (except through your terminal settings)
|
||||
- Leaves its config files in your home folder
|
||||
- FaceBook obviously doesn't endorse or like these kinds of apps and they're likely to break when FaceBook changes stuff in their web app
|
||||
|
||||
## Installation / Usage
|
||||
@@ -38,11 +36,11 @@ Always fresh, always up to date.
|
||||
- Download a release
|
||||
- Put the binary in your PATH (optional)
|
||||
- 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
|
||||
|
||||
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)
|
||||
|
||||
@@ -50,8 +48,5 @@ Some unofficial ways to install via package managers are supported but the insta
|
||||
|
||||
#### Arch Linux (AUR)
|
||||
|
||||
- `git clone https://aur.archlinux.org/whatscli.git`
|
||||
- `cd whatscli`
|
||||
- `makepkg -si`
|
||||
- `yay -S whatscli`
|
||||
- `https://aur.archlinux.org/packages/whatscli/`
|
||||
|
||||
|
||||
128
config/settings.go
Normal file
128
config/settings.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var configFilePath string
|
||||
var keyConfig *cbind.Configuration
|
||||
var cfg *ini.File
|
||||
|
||||
func InitConfig() {
|
||||
var err error
|
||||
if configFilePath, err = xdg.ConfigFile("whatscli/whatscli.config"); err == nil {
|
||||
if cfg, err = ini.Load(configFilePath); err == nil {
|
||||
//TODO: check config for new parameters
|
||||
} else {
|
||||
cfg = ini.Empty()
|
||||
cfg.NewSection("general")
|
||||
cfg.Section("general").NewKey("download_path", GetHomeDir()+"Downloads")
|
||||
cfg.Section("general").NewKey("preview_path", GetHomeDir()+"Downloads")
|
||||
cfg.NewSection("keymap")
|
||||
cfg.Section("keymap").NewKey("switch_panels", "Tab")
|
||||
cfg.Section("keymap").NewKey("focus_messages", "Ctrl+w")
|
||||
cfg.Section("keymap").NewKey("focus_input", "Ctrl+Space")
|
||||
cfg.Section("keymap").NewKey("focus_contacts", "Ctrl+e")
|
||||
cfg.Section("keymap").NewKey("command_connect", "Ctrl+r")
|
||||
cfg.Section("keymap").NewKey("command_quit", "Ctrl+q")
|
||||
cfg.Section("keymap").NewKey("command_help", "Ctrl+?")
|
||||
cfg.Section("keymap").NewKey("message_download", "d")
|
||||
cfg.Section("keymap").NewKey("message_open", "o")
|
||||
cfg.Section("keymap").NewKey("message_show", "s")
|
||||
cfg.Section("keymap").NewKey("message_info", "i")
|
||||
cfg.NewSection("ui")
|
||||
cfg.Section("ui").NewKey("contact_sidebar_width", "30")
|
||||
cfg.NewSection("colors")
|
||||
cfg.Section("colors").NewKey("background", "black")
|
||||
cfg.Section("colors").NewKey("text", "white")
|
||||
cfg.Section("colors").NewKey("list_header", "yellow")
|
||||
cfg.Section("colors").NewKey("list_contact", "green")
|
||||
cfg.Section("colors").NewKey("list_group", "blue")
|
||||
cfg.Section("colors").NewKey("chat_contact", "green")
|
||||
cfg.Section("colors").NewKey("chat_me", "blue")
|
||||
err = cfg.SaveTo(configFilePath)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigFilePath() string {
|
||||
return configFilePath
|
||||
}
|
||||
|
||||
func GetSessionFilePath() string {
|
||||
if sessionFilePath, err := xdg.ConfigFile("whatscli/session"); err == nil {
|
||||
return sessionFilePath
|
||||
}
|
||||
return GetHomeDir() + ".whatscli.session"
|
||||
}
|
||||
|
||||
func GetContactsFilePath() string {
|
||||
if sessionFilePath, err := xdg.ConfigFile("whatscli/contacts"); err == nil {
|
||||
return sessionFilePath
|
||||
}
|
||||
return GetHomeDir() + ".whatscli.session"
|
||||
}
|
||||
|
||||
func GetKey(name string) string {
|
||||
if sec, err := cfg.GetSection("keymap"); err == nil {
|
||||
if key, err := sec.GetKey(name); err == nil {
|
||||
return key.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetColorName(key string) string {
|
||||
if sec, err := cfg.GetSection("colors"); err == nil {
|
||||
if key, err := sec.GetKey(key); err == nil {
|
||||
return key.String()
|
||||
}
|
||||
}
|
||||
return "white"
|
||||
}
|
||||
|
||||
func GetColor(key string) tcell.Color {
|
||||
name := GetColorName(key)
|
||||
if color, ok := tcell.ColorNames[name]; ok {
|
||||
return color
|
||||
}
|
||||
return tcell.ColorWhite
|
||||
}
|
||||
|
||||
func GetSetting(name string) string {
|
||||
if sec, err := cfg.GetSection("general"); err == nil {
|
||||
if key, err := sec.GetKey(name); err == nil {
|
||||
return key.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetIntSetting(section string, name string) int {
|
||||
if sec, err := cfg.GetSection(section); err == nil {
|
||||
if key, err := sec.GetKey(name); err == nil {
|
||||
if val, err := key.Int(); err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// gets the OS home dir with a path separator at the end
|
||||
func GetHomeDir() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
}
|
||||
return usr.HomeDir + string(os.PathSeparator)
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -4,6 +4,7 @@ go 1.15
|
||||
|
||||
require (
|
||||
github.com/Rhymen/go-whatsapp v0.1.1
|
||||
github.com/adrg/xdg v0.2.3
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
||||
@@ -13,8 +14,10 @@ require (
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893
|
||||
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.3
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -8,12 +8,16 @@ github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/g
|
||||
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
|
||||
github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4=
|
||||
github.com/adrg/xdg v0.2.3/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -53,6 +57,7 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893 h1:24As98PZlIdjZn6V4wUulAbYlG7RPg/du9A1FZdT/vs=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893/go.mod h1:0ha5CGekam8ZV1kxkBxSlh7gfQ7YolUj2P/VruwH0QY=
|
||||
@@ -63,6 +68,10 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gitlab.com/tslocum/cbind v0.1.3 h1:FT/fTQ4Yj3eo5021lB3IbkIt8eVtYGhrw/xur+cjvUU=
|
||||
gitlab.com/tslocum/cbind v0.1.3/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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=
|
||||
@@ -89,9 +98,8 @@ golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7 h1:Z991aAXPjz0tLnj74pVXW3eWJ5lHMIBvbRfMq4M2jHA=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -124,5 +132,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
438
main.go
438
main.go
@@ -10,8 +10,11 @@ import (
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/normen/whatscli/config"
|
||||
"github.com/normen/whatscli/messages"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
)
|
||||
|
||||
type waMsg struct {
|
||||
@@ -19,7 +22,7 @@ type waMsg struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
var VERSION string = "v0.6.8"
|
||||
var VERSION string = "v0.7.1"
|
||||
|
||||
var sendChannel chan waMsg
|
||||
var textChannel chan whatsapp.TextMessage
|
||||
@@ -36,26 +39,31 @@ var topBar *tview.TextView
|
||||
|
||||
//var infoBar *tview.TextView
|
||||
var msgStore messages.MessageDatabase
|
||||
var keysApp *cbind.Configuration
|
||||
|
||||
var contactRoot *tview.TreeNode
|
||||
var handler textHandler
|
||||
var app *tview.Application
|
||||
|
||||
func main() {
|
||||
config.InitConfig()
|
||||
msgStore = messages.MessageDatabase{}
|
||||
msgStore.Init()
|
||||
messages.LoadContacts()
|
||||
app = tview.NewApplication()
|
||||
|
||||
sideBarWidth := config.GetIntSetting("ui", "contact_sidebar_width")
|
||||
gridLayout := tview.NewGrid()
|
||||
gridLayout.SetRows(1, 0, 1)
|
||||
gridLayout.SetColumns(30, 0, 30)
|
||||
gridLayout.SetColumns(sideBarWidth, 0, sideBarWidth)
|
||||
gridLayout.SetBorders(true)
|
||||
gridLayout.SetBackgroundColor(tcell.ColorBlack)
|
||||
gridLayout.SetBackgroundColor(config.GetColor("background"))
|
||||
|
||||
topBar = tview.NewTextView()
|
||||
topBar.SetDynamicColors(true)
|
||||
topBar.SetScrollable(false)
|
||||
topBar.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Type /help for help")
|
||||
topBar.SetBackgroundColor(config.GetColor("background"))
|
||||
|
||||
//infoBar = tview.NewTextView()
|
||||
//infoBar.SetDynamicColors(true)
|
||||
@@ -68,142 +76,21 @@ func main() {
|
||||
SetChangedFunc(func() {
|
||||
app.Draw()
|
||||
})
|
||||
|
||||
textView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlE {
|
||||
//TODO: Boilerplate
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlSpace {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return event
|
||||
}
|
||||
if event.Key() == tcell.KeyDown || event.Rune() == 'j' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], 1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[0])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyUp || event.Rune() == 'k' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], -1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'G' {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'g' {
|
||||
textView.Highlight(curRegions[0])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'd' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
DownloadMessageId(hls[0], false)
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'o' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
DownloadMessageId(hls[0], true)
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'i' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
fmt.Fprintln(textView, msgStore.GetMessageInfo(hls[0]))
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 's' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
go PrintImage(hls[0])
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
textView.SetBackgroundColor(config.GetColor("background"))
|
||||
textView.SetTextColor(config.GetColor("text"))
|
||||
|
||||
// TODO: add better way
|
||||
messages.SetTextView(textView)
|
||||
PrintHelp()
|
||||
|
||||
//textView.SetBorder(true)
|
||||
|
||||
textInput = tview.NewInputField()
|
||||
textInput.SetBackgroundColor(config.GetColor("background"))
|
||||
textView.SetTextColor(config.GetColor("text"))
|
||||
textInput.SetChangedFunc(func(change string) {
|
||||
sndTxt = change
|
||||
})
|
||||
textInput.SetDoneFunc(EnterCommand)
|
||||
textInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlE {
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlW {
|
||||
app.SetFocus(textView)
|
||||
if curRegions != nil && len(curRegions) > 0 {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyDown {
|
||||
offset, _ := textView.GetScrollOffset()
|
||||
offset += 1
|
||||
@@ -245,6 +132,7 @@ func main() {
|
||||
PrintError(err)
|
||||
}
|
||||
}()
|
||||
LoadShortcuts()
|
||||
app.Run()
|
||||
}
|
||||
|
||||
@@ -252,10 +140,11 @@ func main() {
|
||||
func MakeTree() *tview.TreeView {
|
||||
rootDir := "Contacts"
|
||||
contactRoot = tview.NewTreeNode(rootDir).
|
||||
SetColor(tcell.ColorYellow)
|
||||
SetColor(config.GetColor("list_header"))
|
||||
treeView = tview.NewTreeView().
|
||||
SetRoot(contactRoot).
|
||||
SetCurrentNode(contactRoot)
|
||||
treeView.SetBackgroundColor(config.GetColor("background"))
|
||||
|
||||
// If a contact was selected, open it.
|
||||
treeView.SetChangedFunc(func(node *tview.TreeNode) {
|
||||
@@ -294,26 +183,233 @@ func MakeTree() *tview.TreeView {
|
||||
return treeView
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleFocusInput(ev *tcell.EventKey) *tcell.EventKey {
|
||||
ResetMsgSelection()
|
||||
if !textInput.HasFocus() {
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleFocusContacts(ev *tcell.EventKey) *tcell.EventKey {
|
||||
ResetMsgSelection()
|
||||
if !treeView.HasFocus() {
|
||||
app.SetFocus(treeView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSwitchPanels(ev *tcell.EventKey) *tcell.EventKey {
|
||||
ResetMsgSelection()
|
||||
if !textInput.HasFocus() {
|
||||
app.SetFocus(textInput)
|
||||
} else {
|
||||
app.SetFocus(treeView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConnect(ev *tcell.EventKey) *tcell.EventKey {
|
||||
msgStore.Init()
|
||||
messages.Login()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleQuit(ev *tcell.EventKey) *tcell.EventKey {
|
||||
messages.Disconnect()
|
||||
app.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHelp(ev *tcell.EventKey) *tcell.EventKey {
|
||||
PrintHelp()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDownload(ev *tcell.EventKey) *tcell.EventKey {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
go DownloadMessageId(hls[0], false)
|
||||
ResetMsgSelection()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleOpen(ev *tcell.EventKey) *tcell.EventKey {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
go DownloadMessageId(hls[0], true)
|
||||
ResetMsgSelection()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleShow(ev *tcell.EventKey) *tcell.EventKey {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
go PrintImage(hls[0])
|
||||
ResetMsgSelection()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleInfo(ev *tcell.EventKey) *tcell.EventKey {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
PrintText(msgStore.GetMessageInfo(hls[0]))
|
||||
ResetMsgSelection()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMessagesUp(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], -1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMessagesDown(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], 1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[0])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMessagesLast(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMessagesFirst(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
textView.Highlight(curRegions[0])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleExitMessages(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return nil
|
||||
}
|
||||
ResetMsgSelection()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadShortcuts() {
|
||||
keysApp = cbind.NewConfiguration()
|
||||
if err := keysApp.Set(config.GetKey("focus_messages"), handleFocusMessage); err != nil {
|
||||
PrintErrorMsg("focus_messages:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("focus_input"), handleFocusInput); err != nil {
|
||||
PrintErrorMsg("focus_input:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("focus_contacts"), handleFocusContacts); err != nil {
|
||||
PrintErrorMsg("focus_contacts:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("switch_panels"), handleSwitchPanels); err != nil {
|
||||
PrintErrorMsg("switch_panels:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("command_connect"), handleConnect); err != nil {
|
||||
PrintErrorMsg("command_connect:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("command_quit"), handleQuit); err != nil {
|
||||
PrintErrorMsg("command_quit:", err)
|
||||
}
|
||||
if err := keysApp.Set(config.GetKey("command_help"), handleHelp); err != nil {
|
||||
PrintErrorMsg("command_help:", err)
|
||||
}
|
||||
app.SetInputCapture(keysApp.Capture)
|
||||
keysMessages := cbind.NewConfiguration()
|
||||
if err := keysMessages.Set(config.GetKey("message_download"), handleDownload); err != nil {
|
||||
PrintErrorMsg("message_download:", err)
|
||||
}
|
||||
if err := keysMessages.Set(config.GetKey("message_open"), handleOpen); err != nil {
|
||||
PrintErrorMsg("message_open:", err)
|
||||
}
|
||||
if err := keysMessages.Set(config.GetKey("message_show"), handleShow); err != nil {
|
||||
PrintErrorMsg("message_show:", err)
|
||||
}
|
||||
if err := keysMessages.Set(config.GetKey("message_info"), handleInfo); err != nil {
|
||||
PrintErrorMsg("message_info:", err)
|
||||
}
|
||||
keysMessages.SetKey(tcell.ModNone, tcell.KeyEscape, handleExitMessages)
|
||||
keysMessages.SetKey(tcell.ModNone, tcell.KeyUp, handleMessagesUp)
|
||||
keysMessages.SetKey(tcell.ModNone, tcell.KeyDown, handleMessagesDown)
|
||||
keysMessages.SetRune(tcell.ModNone, 'k', handleMessagesUp)
|
||||
keysMessages.SetRune(tcell.ModNone, 'j', handleMessagesDown)
|
||||
keysMessages.SetRune(tcell.ModNone, 'g', handleMessagesFirst)
|
||||
keysMessages.SetRune(tcell.ModNone, 'G', handleMessagesLast)
|
||||
textView.SetInputCapture(keysMessages.Capture)
|
||||
}
|
||||
|
||||
// prints help to chat view
|
||||
func PrintHelp() {
|
||||
fmt.Fprintln(textView, "[::b]WhatsCLI "+VERSION+"[-]")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::u]Keys:[-::-]")
|
||||
fmt.Fprintln(textView, "<Tab> = switch input/contacts")
|
||||
fmt.Fprintln(textView, "<Up/Dn> = scroll history/contacts")
|
||||
fmt.Fprintln(textView, "<Ctrl-W> = focus chat window")
|
||||
fmt.Fprintln(textView, "[::b] Up/Down[::-] = scroll history/contacts")
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("switch_panels"), "[::-] = switch input/contacts")
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("focus_messages"), "[::-] = focus message panel")
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("focus_contacts"), "[::-] = focus contacts panel")
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("focus_input"), "[::-] = focus input")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::-]Chat window focused:[-::-]")
|
||||
fmt.Fprintln(textView, "<Up/Down> = select message")
|
||||
fmt.Fprintln(textView, "<d> = download attachment to $HOME/Downloads")
|
||||
fmt.Fprintln(textView, "<o> = download & open attachment")
|
||||
fmt.Fprintln(textView, "<s> = download & show image in chat using jp2a command")
|
||||
fmt.Fprintln(textView, "[-::-]Message panel focused:[-::-]")
|
||||
fmt.Fprintln(textView, "[::b] Up/Down[::-] = select message")
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("message_download"), "[::-] = download attachment -> ", config.GetSetting("download_path"))
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("message_open"), "[::-] = download & open attachment -> ", config.GetSetting("preview_path"))
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("message_show"), "[::-] = download & show image using jp2a -> ", config.GetSetting("preview_path"))
|
||||
fmt.Fprintln(textView, "[::b]", config.GetKey("message_info"), "[::-] = info about message")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
|
||||
fmt.Fprintln(textView, "/connect = (re)connect in case the connection dropped")
|
||||
fmt.Fprintln(textView, "/quit = exit app")
|
||||
fmt.Fprintln(textView, "/help = show this help")
|
||||
fmt.Fprintln(textView, "[::b] /connect[::-] = (re)connect in case the connection dropped ->[::b]", config.GetKey("command_connect"), "[::-]")
|
||||
fmt.Fprintln(textView, "[::b] /help[::-] = show this help ->[::b]", config.GetKey("command_help"), "[::-]")
|
||||
fmt.Fprintln(textView, "[::b] /quit[::-] = exit app ->[::b]", config.GetKey("command_quit"), "[::-]")
|
||||
fmt.Fprintln(textView, "[::b] /disconnect[::-] = close the connection")
|
||||
fmt.Fprintln(textView, "[::b] /logout[::-] = remove login data from computer (stays connected until app closes)")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "Config file in \n-> ", config.GetConfigFilePath())
|
||||
}
|
||||
|
||||
// called when text is entered by the user
|
||||
@@ -332,6 +428,17 @@ func EnterCommand(key tcell.Key) {
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/disconnect" {
|
||||
PrintError(messages.Disconnect())
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/logout" {
|
||||
//command
|
||||
PrintError(messages.Logout())
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/load" {
|
||||
//command
|
||||
LoadContacts()
|
||||
@@ -346,9 +453,16 @@ func EnterCommand(key tcell.Key) {
|
||||
}
|
||||
if sndTxt == "/quit" {
|
||||
//command
|
||||
messages.Disconnect()
|
||||
app.Stop()
|
||||
return
|
||||
}
|
||||
if sndTxt == "/keys" {
|
||||
//command
|
||||
//config.PrintKeys(textView)
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if strings.Index(sndTxt, "/addname ") == 0 {
|
||||
//command
|
||||
parts := strings.Split(sndTxt, " ")
|
||||
@@ -407,6 +521,14 @@ func GetOffsetMsgId(curId string, offset int) string {
|
||||
}
|
||||
}
|
||||
|
||||
// resets the selection in the textView and scrolls it down
|
||||
func ResetMsgSelection() {
|
||||
if len(textView.GetHighlights()) > 0 {
|
||||
textView.Highlight("")
|
||||
}
|
||||
textView.ScrollToEnd()
|
||||
}
|
||||
|
||||
// prints a text message to the TextView
|
||||
func PrintTextMessage(msg whatsapp.TextMessage) {
|
||||
fmt.Fprintln(textView, messages.GetTextMessageString(&msg))
|
||||
@@ -419,15 +541,26 @@ func PrintText(txt string) {
|
||||
|
||||
// prints an error to the TextView
|
||||
func PrintError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(textView, "[red]", err.Error(), "[-]")
|
||||
}
|
||||
|
||||
// prints an error to the TextView
|
||||
func PrintErrorMsg(text string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(textView, "[red]", text, err.Error(), "[-]")
|
||||
}
|
||||
|
||||
// prints an image attachment to the TextView (by message id)
|
||||
func PrintImage(id string) {
|
||||
var err error
|
||||
var path string
|
||||
PrintText("[::d]loading..[::-]")
|
||||
if path, err = msgStore.DownloadMessage(id, false); err == nil {
|
||||
if path, err = msgStore.DownloadMessage(id, true); err == nil {
|
||||
cmd := exec.Command("jp2a", "--color", path)
|
||||
var stdout io.ReadCloser
|
||||
if stdout, err = cmd.StdoutPipe(); err == nil {
|
||||
@@ -441,16 +574,17 @@ func PrintImage(id string) {
|
||||
PrintError(err)
|
||||
}
|
||||
|
||||
// initiates a download of a specific message attachment in a new go routine
|
||||
func DownloadMessageId(id string, open bool) {
|
||||
// downloads a specific message attachment
|
||||
func DownloadMessageId(id string, openIt bool) {
|
||||
PrintText("[::d]loading..[::-]")
|
||||
go func() {
|
||||
if result, err := msgStore.DownloadMessage(id, open); err == nil {
|
||||
fmt.Fprintln(textView, "[::d]Downloaded as [yellow]", result, "[-::-]")
|
||||
} else {
|
||||
PrintError(err)
|
||||
if result, err := msgStore.DownloadMessage(id, openIt); err == nil {
|
||||
PrintText("[::d]Downloaded as [yellow]" + result + "[-::-]")
|
||||
if openIt {
|
||||
open.Run(result)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
PrintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// notifies about a new message if its recent
|
||||
@@ -473,9 +607,9 @@ func LoadContacts() {
|
||||
SetReference(element).
|
||||
SetSelectable(true)
|
||||
if strings.Count(element, messages.CONTACTSUFFIX) > 0 {
|
||||
node.SetColor(tcell.ColorGreen)
|
||||
node.SetColor(config.GetColor("list_contact"))
|
||||
} else {
|
||||
node.SetColor(tcell.ColorBlue)
|
||||
node.SetColor(config.GetColor("list_group"))
|
||||
}
|
||||
contactRoot.AddChild(node)
|
||||
if element == currentReceiver {
|
||||
@@ -552,7 +686,7 @@ type textHandler struct{}
|
||||
|
||||
// HandleError implements the error handler interface for go-whatsapp
|
||||
func (t textHandler) HandleError(err error) {
|
||||
// TODO : handle go routine here
|
||||
PrintText("[red]go-whatsapp reported an error:[-]")
|
||||
PrintError(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/normen/whatscli/config"
|
||||
)
|
||||
|
||||
var contacts map[string]string
|
||||
@@ -15,9 +16,16 @@ var connection *whatsapp.Conn
|
||||
// loads custom contacts from disk
|
||||
func LoadContacts() {
|
||||
contacts = make(map[string]string)
|
||||
file, err := os.Open(GetHomeDir() + ".whatscli.contacts")
|
||||
file, err := os.Open(config.GetContactsFilePath())
|
||||
if err != nil {
|
||||
return
|
||||
// load old contacts file, re-save in new location if found
|
||||
file, err = os.Open(GetHomeDir() + ".whatscli.contacts")
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
os.Remove(GetHomeDir() + ".whatscli.contacts")
|
||||
SaveContacts()
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
@@ -29,7 +37,7 @@ func LoadContacts() {
|
||||
|
||||
// saves custom contacts to disk
|
||||
func SaveContacts() {
|
||||
file, err := os.Create(GetHomeDir() + ".whatscli.contacts")
|
||||
file, err := os.Open(config.GetContactsFilePath())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,15 +3,19 @@ package messages
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/rivo/tview"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/normen/whatscli/config"
|
||||
"github.com/normen/whatscli/qrcode"
|
||||
)
|
||||
|
||||
var textView *tview.TextView
|
||||
var connMutex sync.Mutex
|
||||
|
||||
// TODO: remove this circular dependeny in favor of a better way
|
||||
func SetTextView(tv *tview.TextView) {
|
||||
@@ -20,6 +24,8 @@ func SetTextView(tv *tview.TextView) {
|
||||
|
||||
// gets an existing connection or creates one
|
||||
func GetConnection() *whatsapp.Conn {
|
||||
connMutex.Lock()
|
||||
defer connMutex.Unlock()
|
||||
var wac *whatsapp.Conn
|
||||
if connection == nil {
|
||||
wacc, err := whatsapp.NewConn(5 * time.Second)
|
||||
@@ -44,8 +50,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
|
||||
// new one using qr scanned on the terminal.
|
||||
func LoginWithConnection(wac *whatsapp.Conn) error {
|
||||
if connection != nil && connection.GetConnected() {
|
||||
connection.Disconnect()
|
||||
connMutex.Lock()
|
||||
defer connMutex.Unlock()
|
||||
if wac != nil && wac.GetConnected() {
|
||||
wac.Disconnect()
|
||||
}
|
||||
//load saved session
|
||||
session, err := readSession()
|
||||
@@ -78,17 +86,34 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Disconnect() error {
|
||||
wac := GetConnection()
|
||||
if wac != nil && wac.GetConnected() {
|
||||
_, err := wac.Disconnect()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout logs out the user.
|
||||
func Logout() error {
|
||||
connMutex.Lock()
|
||||
defer connMutex.Unlock()
|
||||
return removeSession()
|
||||
}
|
||||
|
||||
// reads the session file from disk
|
||||
func readSession() (whatsapp.Session, error) {
|
||||
session := whatsapp.Session{}
|
||||
file, err := os.Open(GetHomeDir() + ".whatscli.session")
|
||||
file, err := os.Open(config.GetSessionFilePath())
|
||||
if err != nil {
|
||||
return session, err
|
||||
// load old session file, delete if found
|
||||
file, err = os.Open(GetHomeDir() + ".whatscli.session")
|
||||
if err != nil {
|
||||
return session, err
|
||||
} else {
|
||||
os.Remove(GetHomeDir() + ".whatscli.session")
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
@@ -101,7 +126,7 @@ func readSession() (whatsapp.Session, error) {
|
||||
|
||||
// saves the session file to disk
|
||||
func writeSession(session whatsapp.Session) error {
|
||||
file, err := os.Create(GetHomeDir() + ".whatscli.session")
|
||||
file, err := os.Create(config.GetSessionFilePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,5 +141,5 @@ func writeSession(session whatsapp.Session) error {
|
||||
|
||||
// deletes the session file from disk
|
||||
func removeSession() error {
|
||||
return os.Remove(GetHomeDir() + ".whatscli.session")
|
||||
return os.Remove(config.GetSessionFilePath())
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/normen/whatscli/config"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
const GROUPSUFFIX = "@g.us"
|
||||
@@ -21,41 +22,46 @@ 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
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// initialize the database
|
||||
func (db *MessageDatabase) Init() {
|
||||
//var this = *db
|
||||
(*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)
|
||||
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 {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
//var this = *db
|
||||
var didNew = false
|
||||
var wid = msg.Info.RemoteJid
|
||||
if (*db).textMessages[wid] == nil {
|
||||
if db.textMessages[wid] == nil {
|
||||
var newArr = []*whatsapp.TextMessage{}
|
||||
(*db).textMessages[wid] = newArr
|
||||
(*db).latestMessage[wid] = msg.Info.Timestamp
|
||||
db.textMessages[wid] = newArr
|
||||
db.latestMessage[wid] = msg.Info.Timestamp
|
||||
didNew = true
|
||||
} else if (*db).latestMessage[wid] < msg.Info.Timestamp {
|
||||
(*db).latestMessage[wid] = msg.Info.Timestamp
|
||||
} else if db.latestMessage[wid] < msg.Info.Timestamp {
|
||||
db.latestMessage[wid] = msg.Info.Timestamp
|
||||
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
|
||||
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
|
||||
})
|
||||
return didNew
|
||||
}
|
||||
|
||||
// add audio/video/image/doc message, stored by message id
|
||||
func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
var id = ""
|
||||
switch v := (*msg).(type) {
|
||||
default:
|
||||
@@ -69,31 +75,35 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
id = v.Info.Id
|
||||
}
|
||||
if id != "" {
|
||||
(*db).otherMessages[id] = msg
|
||||
db.otherMessages[id] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// get an array of all chat ids
|
||||
func (db *MessageDatabase) GetContactIds() []string {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
//var this = *db
|
||||
keys := make([]string, len((*db).textMessages))
|
||||
keys := make([]string, len(db.textMessages))
|
||||
i := 0
|
||||
for k := range (*db).textMessages {
|
||||
for k := range db.textMessages {
|
||||
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]] > db.latestMessage[keys[j]]
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
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[-]"
|
||||
}
|
||||
out := ""
|
||||
if msg, ok := (*db).messagesById[id]; ok {
|
||||
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"
|
||||
@@ -106,10 +116,12 @@ func (db *MessageDatabase) GetMessageInfo(id string) string {
|
||||
|
||||
// get a string containing all messages for a chat by chat id
|
||||
func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
//var this = *db
|
||||
var out = ""
|
||||
var arr = []string{}
|
||||
for _, element := range (*db).textMessages[wid] {
|
||||
for _, element := range db.textMessages[wid] {
|
||||
out += GetTextMessageString(element)
|
||||
out += "\n"
|
||||
arr = append(arr, element.Info.Id)
|
||||
@@ -117,29 +129,11 @@ func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
|
||||
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
|
||||
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) {
|
||||
default:
|
||||
case whatsapp.ImageMessage:
|
||||
@@ -156,9 +150,17 @@ func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
|
||||
}
|
||||
|
||||
// attempts to download a messages attachments, returns path or error message
|
||||
func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) {
|
||||
if msg, ok := (*db).otherMessages[wid]; ok {
|
||||
var fileName string = GetHomeDir() + "Downloads" + string(os.PathSeparator)
|
||||
func (db *MessageDatabase) DownloadMessage(wid string, preview bool) (string, error) {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
if msg, ok := db.otherMessages[wid]; ok {
|
||||
var fileName string = ""
|
||||
if preview {
|
||||
fileName += config.GetSetting("download_path")
|
||||
} else {
|
||||
fileName += config.GetSetting("preview_path")
|
||||
}
|
||||
fileName += string(os.PathSeparator)
|
||||
switch v := (*msg).(type) {
|
||||
default:
|
||||
case whatsapp.ImageMessage:
|
||||
@@ -167,7 +169,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
return saveAttachment(data, fileName)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
@@ -178,7 +180,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
return saveAttachment(data, fileName)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
@@ -189,7 +191,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
return saveAttachment(data, fileName)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
@@ -200,7 +202,7 @@ func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
return saveAttachment(data, fileName)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
@@ -210,15 +212,31 @@ 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, path string, openIt bool) (string, error) {
|
||||
err := ioutil.WriteFile(path, data, 0644)
|
||||
if err == nil {
|
||||
if openIt {
|
||||
open.Run(path)
|
||||
}
|
||||
} else {
|
||||
return path, err
|
||||
// create a formatted string with regions based on message ID from a text message
|
||||
func GetTextMessageString(msg *whatsapp.TextMessage) string {
|
||||
colorMe := config.GetColorName("chat_me")
|
||||
colorContact := config.GetColorName("chat_contact")
|
||||
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]" + GetIdShort(userId) + ": [-::-]" + text
|
||||
} else { // message from others
|
||||
out += "[-::d](" + time + ") [" + colorContact + "::b]" + GetIdShort(msg.Info.RemoteJid) + ": [-::-]" + text
|
||||
}
|
||||
return path, nil
|
||||
out += "[\"\"]"
|
||||
return out
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user