Compare commits

...

66 Commits

Author SHA1 Message Date
normen
c1897b475a allow backlog to be read, improve code separation 2020-11-23 04:44:42 +01:00
normen
1672b42f7e avoid storing messages twice 2020-11-22 21:35:04 +01:00
normen
8a28ea47fd fix fallback value for contacts path 2020-11-22 17:20:07 +01:00
normen
3450fbc78f allow configuring input background and text color separately 2020-11-22 16:22:13 +01:00
normen
48ad9ce669 update links 2020-11-22 15:18:32 +01:00
normen
fd676f13cf - fix text input text color not being set
- Fixes #20
2020-11-22 15:02:38 +01:00
normen
63b5f3a604 update links 2020-11-22 14:54:53 +01:00
normen
b20031ff6a update links 2020-11-22 14:46:51 +01:00
normen
00516c3191 update links 2020-11-22 14:34:51 +01:00
Normen Hansen
76c4010ce2 Create FUNDING.yml 2020-11-22 14:03:54 +01:00
normen
0b8d265024 remove old keybindings 2020-11-22 13:21:38 +01:00
normen
80825b0dff small cleanups 2020-11-22 13:17:27 +01:00
normen
42daf1a9f7 add configuration system 2020-11-22 04:35:03 +01:00
normen
04960123da fix freeze when downloading messages 2020-11-21 21:13:38 +01:00
normen
ab03aeb7a0 error output and README improvements 2020-11-20 14:40:01 +01:00
normen
f6860b56b8 secure connection handling with mutex 2020-11-20 14:31:17 +01:00
normen
6bea425367 make message database thread safe 2020-11-20 14:10:09 +01:00
normen
1b3379b613 learning GO - using its sugar 2020-11-20 13:24:15 +01:00
normen
1cc68c9203 fix /connect behavior 2020-11-20 00:05:22 +01:00
normen
c3a6f98b13 make /connect command work, revert in-memory images 2020-11-19 23:47:43 +01:00
normen
21fec5a5d0 don't re-download stuff 2020-11-19 23:42:35 +01:00
normen
b2b798c514 only load messages to memory for inline display 2020-11-19 20:38:02 +01:00
normen
5d63cf41eb improve README layout 2020-11-19 18:01:01 +01:00
normen
e519c4a892 small fix in release script 2020-11-19 17:10:09 +01:00
normen
754106de62 fix last messages in open chat not being selectable 2020-11-19 17:05:21 +01:00
normen
07474c5b3d improve release script 2020-11-19 16:49:29 +01:00
normen
b142aef93c add homebrew tap update to release script 2020-11-19 16:40:59 +01:00
normen
babe81c1aa add info about package managers 2020-11-19 14:11:01 +01:00
normen
b89ecfec79 add screenshot 2020-11-19 12:36:53 +01:00
normen
5e7659b845 clean/update dependencies 2020-11-19 12:28:56 +01:00
normen
eee49d713d upadate docs, use color in jp2a 2020-11-19 04:40:43 +01:00
normen
2a6b0efe68 always scroll back chat view when exiting 2020-11-19 04:28:35 +01:00
normen
9ee28a011c improve focus and highlight handling 2020-11-19 04:03:51 +01:00
normen
16a6519ba7 add s key to help 2020-11-19 03:05:59 +01:00
normen
bb00a9a8f0 allow displaying images using jp2a 2020-11-19 03:04:22 +01:00
normen
4ef8b76a55 report full path on download 2020-11-19 02:59:01 +01:00
normen
6a832cbcf3 reduce data copying 2020-11-19 01:08:22 +01:00
normen
c2f3a012dc remove highlights when pressing <Esc> or o/d 2020-11-19 00:24:27 +01:00
normen
147feb90ec update README 2020-11-19 00:04:48 +01:00
normen
b39d4891e0 fix inverted logic for selecting messages 2020-11-19 00:02:21 +01:00
normen
19455b0baa add some code documentation 2020-11-18 23:58:28 +01:00
normen
a37e6c7d3b fix selection when highlight is not visible 2020-11-18 23:36:13 +01:00
normen
82d2a6637d allow navigating chat window selection 2020-11-18 23:30:02 +01:00
normen
1eb3af823e move session_manager to messages package 2020-11-18 22:53:00 +01:00
normen
773f9b783d add support for opening attachments 2020-11-18 22:39:53 +01:00
normen
e290a79064 add makefile for VIM 2020-11-18 13:23:49 +01:00
normen
7ae3967fbc improve contact handling, use short name in chat 2020-11-18 13:23:19 +01:00
normen
5045ac59f5 use short name first when loading name from web 2020-11-18 01:11:58 +01:00
normen
2db7e51327 update README 2020-11-18 01:05:24 +01:00
normen
e698b66819 use go-whatsapps built-in contacts list (d'oh!) 2020-11-18 01:00:51 +01:00
normen
7fdafb477a bump version to 0.5.0 2020-11-17 21:52:38 +01:00
normen
13632156a1 update go-whatsapp to master, remove send workaround 2020-11-17 21:49:44 +01:00
normen
08b69673e3 don't send message on <esc>, delete input instead 2020-11-17 21:39:27 +01:00
normen
36b8bdac2f update dependencies 2020-11-17 20:45:39 +01:00
normen
43c5795cc7 fix formatting 2020-11-17 18:54:16 +01:00
normen
2063aeec91 add Ctrl-E and Ctrl-Space to switch panes 2020-11-17 18:50:18 +01:00
normen
310e9fa2ab use println instead of print 2020-11-17 16:24:59 +01:00
normen
c860542052 add some code documentation 2020-11-17 02:34:51 +01:00
normen
172fad115b add /help command 2020-11-17 00:45:01 +01:00
normen
cfd41b2de6 code cleanups, red error messages 2020-11-17 00:21:43 +01:00
normen
6eb872ef8a edit release message before release 2020-11-17 00:01:12 +01:00
normen
3f5ec9ff8c switch day/month in messages display 2020-11-16 23:58:27 +01:00
normen
48fca0e6ad clean up module imports 2020-11-16 23:29:33 +01:00
normen
28d06730c7 add usage reminder to release script 2020-11-16 22:23:56 +01:00
normen
fff039e776 update README, add release script 2020-11-16 22:08:08 +01:00
normen
82f6795aef fix border background 2020-11-16 21:20:59 +01:00
14 changed files with 1443 additions and 495 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
open_collective: #normen
ko_fi: normen
patreon: #normen
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

22
Makefile Normal file
View File

@@ -0,0 +1,22 @@
# Simple Makefile for go
build:
go build
clean:
go clean
run:
go run .
install:
go install .
get:
go get
update:
go get -u
release:
./release.sh

View File

@@ -1,66 +1,52 @@
# whatscli
A command line interface for whatsapp, based on go-whatsapp and tview
A command line interface for whatsapp, based on [go-whatsapp](https://github.com/Rhymen/go-whatsapp) and [tview](https://github.com/rivo/tview)
```
┌────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ WhatsCLI v0.1.0 Help: /name = name contact | /quit = exit app | /load = reload contacts | <Tab> =│
├──────────────────────────────┬─────────────────────────────────────────────────────────────────────┤
│Contacts │ │
│├──Normen │(03-22-13 21:18:24) Daniel: Sachste bescheid wenn der kram vorbei ist│
│├──Lilou │? :) │
│├──Boris │(03-22-13 21:37:29) Ich: Jo │
│├──Malte │(03-22-13 22:02:13) Daniel: Mensch das geht ja lang.. │
│├──Daniel │(03-22-13 22:02:53) Ich: Nu is schulz │
│├──Seb │(03-22-13 22:03:08) Daniel: Jo bis gleich ! │
│├──Bettina │(04-07-14 18:06:15) Daniel: Hey wie schauts :) │
│├──Elternbeirat │(04-07-14 18:07:56) Ich: Ich komme laut Navigationssystem um 19:40 │
│├──4911758758720-1565273421@g.│Uhr an │
│├──491111250015@s.whatsapp.net│(04-07-14 18:08:33) Daniel: Sauber, ruf ma so ca 10 min vorher durch │
│├──4911758758714-1537904683@g.│dann bin ich da :) │
│├──491114171906-1448397341@g.u│(04-07-14 19:24:21) Ich: Bin jetzt in Bremen. Circa 10 Minuten │
│├──491192855547-1561396191@g.u│(04-07-14 19:24:45) Daniel: Ok mach mich los │
│├──491152456088@s.whatsapp.net│(07-27-14 19:14:19) Ich: Moin do. Sag' mal bist Du morgen um fünf in │
│├──491107382606-1364411990@g.u│Bremen? Ich bräuchte jemanden um das Mischpult etc. wieder in einen │
│├──491111250017-1603197565@g.u│LKW zu laden.. │
│├──491131942996@s.whatsapp.net│(07-27-14 19:15:22) Daniel: Ich bin unterwegs, sorry │
│├──491122978981@s.whatsapp.net│(07-27-14 19:15:52) Ich: Kein Ding, danke! │
│├──491192855528@s.whatsapp.net│(07-27-14 19:24:50) Daniel: Jou bin quasi noch im breminale stress :)│
│├──491154447429@s.whatsapp.net│(07-27-14 19:25:12) Ich: Na dann noch viel Spass :) │
│├──491132457405-1526385826@g.u│(07-27-14 19:25:34) Daniel: Ja danke, bin froh wenns vorbei is ;) │
│├──491103663035@s.whatsapp.net│(07-27-14 19:26:12) Ich: Augen zu und durch, Lock'n'Loll │
│├──491113075747@s.whatsapp.net│(11-15-20 15:27:06) Ich: testjj │
│├──491147048885@s.whatsapp.net├─────────────────────────────────────────────────────────────────────┤
│├──491124146101@s.whatsapp.net│ │
└──────────────────────────────┴─────────────────────────────────────────────────────────────────────┘
```
![whatscli-screenshot](/doc/screenshot.png?raw=true "WhatsCLI 0.6.5")
## Features
Things that work.
- Allows sending and receiving WhatsApp messages in a CLI interface
- Connects through Web App API without browser
- Connects through the Web App API without a browser
- Allows sending and receiving WhatsApp messages in a command line app
- Allows downloading and opening image/video/audio/document attachments
- Uses QR code for simple setup
- Binaries for Windows, Mac, Linux and RaspBerry Pi
### Caveats
This is a WIP. Heres some things you might expect to work that don't. Plus some other things I should mention.
This is a WIP and mainly meant for my personal use. Heres some things you might expect to work that don't. Plus some other things I should mention.
- Only lists contacts and groups that have been messaged on phone
- Only fetches a few messages for last contacted
- To display names they have to be entered through the `/name` or `/addname` commands for each contact
- No support for images, videos, documents etc
- Only shows existing chats
- Only fetches a few old messages
- No incoming message notification / count
- Not configurable at all
- 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 they change stuff in their web app
- No proper connection drop handling
- No uploading of images/video/audio/data
- 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
How to get it running and use it
How to get it running and how to use it
### Latest Release
Always fresh, always up to date.
- Download a release
- Put the binary in your PATH (optional)
- Run with `whatscli` (or double-click)
- Scan QR code with WhatsApp on 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 ways to install via package managers are supported but the installed version might be out of date.
#### MacOS (homebrew)
- `brew install normen/tap/whatscli`
#### Arch Linux (AUR)
- `https://aur.archlinux.org/packages/whatscli/`

152
config/settings.go Normal file
View File

@@ -0,0 +1,152 @@
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
if section, err := cfg.GetSection("keymap"); err == nil {
if _, err := section.GetKey("command_backlog"); err != nil {
section.NewKey("command_backlog", "Ctrl+b")
err = cfg.SaveTo(configFilePath)
}
}
if section, err := cfg.GetSection("colors"); err == nil {
if _, err := section.GetKey("borders"); err != nil {
section.NewKey("borders", "white")
err = cfg.SaveTo(configFilePath)
}
if _, err := section.GetKey("input_background"); err != nil {
section.NewKey("input_background", "blue")
err = cfg.SaveTo(configFilePath)
}
if _, err := section.GetKey("input_text"); err != nil {
section.NewKey("input_text", "white")
err = cfg.SaveTo(configFilePath)
}
}
} 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_backlog", "Ctrl+b")
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")
cfg.Section("colors").NewKey("borders", "white")
cfg.Section("colors").NewKey("input_background", "blue")
cfg.Section("colors").NewKey("input_text", "white")
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.contacts"
}
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)
}

BIN
doc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

22
go.mod
View File

@@ -3,11 +3,21 @@ module github.com/normen/whatscli
go 1.15
require (
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a
github.com/gdamore/tcell v1.4.0
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/mattn/go-colorable v0.1.1
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9
github.com/golang/protobuf v1.4.3 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/mattn/go-colorable v0.1.8
github.com/pkg/errors v0.9.1 // indirect
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
)

125
go.sum
View File

@@ -1,57 +1,140 @@
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp v0.1.0 h1:XTXhFIQ/fx9jKObUnUX2Q+nh58EyeHNhX7DniE8xeuA=
github.com/Rhymen/go-whatsapp v0.1.0/go.mod h1:xJSy+okeRjKkQEH/lEYrnekXB3PG33fqL0I6ncAkV50=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a h1:LW+rX0NY6LzMPa2hJcgmQlfiFJUihzOMAaIoCq+P3xc=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp v0.1.1 h1:OK+bCugQcr2YjyYKeDzULqCtM50TPUFM6LvQtszKfcw=
github.com/Rhymen/go-whatsapp v0.1.1/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
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 v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
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=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743 h1:9BBjVJTRxuYBeCAv9DFH2hSzY0ujLx5sxMg5D3K/Xeg=
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743/go.mod h1:t7mcA3nlK9dxD1DMoz/DQRMWFMkGBUj6rJBM5VNfLFA=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
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=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
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 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
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/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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg=
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-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=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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=

716
main.go
View File

@@ -1,78 +1,62 @@
package main
import (
"bufio"
"fmt"
"github.com/Rhymen/go-whatsapp"
"github.com/gdamore/tcell/v2"
"github.com/normen/whatscli/messages"
"github.com/rivo/tview"
"io"
"os/exec"
"strings"
"time"
"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 {
Wid string
Text string
}
var VERSION string = "v0.4.1"
var sendChannel chan waMsg
var textChannel chan whatsapp.TextMessage
var contactChannel chan whatsapp.Contact
var VERSION string = "v0.8.0"
var sndTxt string = ""
var currentReceiver string = ""
var curRegions []string
var textView *tview.TextView
var treeView *tview.TreeView
var textInput *tview.InputField
var topBar *tview.TextView
//var infoBar *tview.TextView
var connection *whatsapp.Conn
var msgStore messages.MessageDatabase
var sessionManager *messages.SessionManager
var keyBindings *cbind.Configuration
var contactRoot *tview.TreeNode
var handler textHandler
var app *tview.Application
//var messages map[string]string
var uiHandler messages.UiMessageHandler
func main() {
msgStore = messages.MessageDatabase{}
msgStore.Init()
config.InitConfig()
uiHandler = UiHandler{}
sessionManager = &messages.SessionManager{}
sessionManager.Init(uiHandler)
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)
//list := tview.NewList()
////list.SetTitle("Contacts")
////list.AddItem("List Contacts", "get the contacts", 'a', func() {
//// list.Clear()
//// var ids = msgStore.GetContactIds()
//// for _, element := range ids {
//// //fmt.Fprint(textView, "\n"+element)
//// var elem = element
//// list.AddItem(messages.GetIdName(element), "", '-', func() {
//// currentReceiver = elem
//// textView.Clear()
//// textView.SetText(msgStore.GetMessagesString(elem))
//// fmt.Fprint(textView, "\nNeuer Empfänger: ", elem)
//// })
//// }
////})
//list.ShowSecondaryText(false)
//list.AddItem("Load", "Load Contacts", 'l', LoadContacts)
//list.AddItem("Quit", "Press to exit", 'q', func() {
// app.Stop()
//})
gridLayout.SetBackgroundColor(config.GetColor("background"))
gridLayout.SetBordersColor(config.GetColor("borders"))
topBar = tview.NewTextView()
topBar.SetDynamicColors(true)
topBar.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Help: /name NewName | /addname 123456 NewName | /quit | <Tab> = contacts/message | <Up/Dn> = scroll")
topBar.SetScrollable(false)
topBar.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Type /help for help")
topBar.SetBackgroundColor(config.GetColor("background"))
//infoBar = tview.NewTextView()
//infoBar.SetDynamicColors(true)
@@ -85,21 +69,22 @@ func main() {
SetChangedFunc(func() {
app.Draw()
})
textView.SetBackgroundColor(config.GetColor("background"))
textView.SetTextColor(config.GetColor("text"))
fmt.Fprint(textView, "[::b]WhatsCLI "+VERSION+"\n\n[-][-::u]Commands:[-::-]\n/name NewName = name current contact\n/addname number NewName = name by number\n/load = reload contacts\n/quit = exit app\n\n[-::u]Keys:[-::-]\n<Tab> = switch input/contacts\n<Up/Dn> = scroll history")
//textView.SetBorder(true)
// TODO: add better way
sessionManager.SetTextView(textView)
PrintHelp()
textInput = tview.NewInputField()
textInput.SetBackgroundColor(config.GetColor("background"))
textInput.SetFieldBackgroundColor(config.GetColor("input_background"))
textInput.SetFieldTextColor(config.GetColor("input_text"))
textInput.SetChangedFunc(func(change string) {
sndTxt = change
})
textInput.SetDoneFunc(EnterCommand)
textInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyTab {
app.SetFocus(treeView)
return nil
}
if event.Key() == tcell.KeyDown {
offset, _ := textView.GetScrollOffset()
offset += 1
@@ -137,69 +122,23 @@ func main() {
app.EnableMouse(true)
app.SetFocus(textInput)
go func() {
if err := StartTextReceiver(); err != nil {
fmt.Fprint(textView, err)
if err := sessionManager.StartTextReceiver(); err != nil {
PrintError(err)
}
}()
LoadShortcuts()
app.Run()
}
func EnterCommand(key tcell.Key) {
if sndTxt == "" {
return
}
if sndTxt == "/load" {
//command
LoadContacts()
textInput.SetText("")
return
}
if sndTxt == "/quit" {
//command
app.Stop()
return
}
if strings.Index(sndTxt, "/addname ") == 0 {
//command
parts := strings.Split(sndTxt, " ")
if len(parts) < 3 {
fmt.Fprint(textView, "\nUse /addname 1234567 NewName")
return
}
messages.SetIdName(parts[1]+messages.CONTACTSUFFIX, strings.TrimPrefix(sndTxt, "/addname "+parts[1]+" "))
SetDisplayedContact(currentReceiver)
LoadContacts()
textInput.SetText("")
return
}
if currentReceiver == "" {
fmt.Fprint(textView, "\nNo recipient set")
return
}
if strings.Index(sndTxt, "/name ") == 0 {
//command
messages.SetIdName(currentReceiver, strings.TrimPrefix(sndTxt, "/name "))
SetDisplayedContact(currentReceiver)
LoadContacts()
textInput.SetText("")
return
}
// send message
msg := waMsg{
Wid: currentReceiver,
Text: sndTxt,
}
sendChannel <- msg
textInput.SetText("")
}
// creates the TreeView for contacts
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) {
@@ -217,175 +156,440 @@ func MakeTree() *tview.TreeView {
node.SetExpanded(!node.IsExpanded())
}
})
treeView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyTab {
app.SetFocus(textInput)
return nil
}
return event
})
return treeView
}
func LoadContacts() {
var ids = msgStore.GetContactIds()
contactRoot.ClearChildren()
for _, element := range ids {
node := tview.NewTreeNode(messages.GetIdName(element)).
SetReference(element).
SetSelectable(true)
if strings.Count(element, messages.CONTACTSUFFIX) > 0 {
node.SetColor(tcell.ColorGreen)
} else {
node.SetColor(tcell.ColorBlue)
}
contactRoot.AddChild(node)
if element == currentReceiver {
treeView.SetCurrentNode(node)
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 handleCommand(command string) func(ev *tcell.EventKey) *tcell.EventKey {
return func(ev *tcell.EventKey) *tcell.EventKey {
sessionManager.CommandChannel <- command
return nil
}
}
func handleQuit(ev *tcell.EventKey) *tcell.EventKey {
sessionManager.CommandChannel <- "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 {
//TODO: command msg info
//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() {
keyBindings = cbind.NewConfiguration()
if err := keyBindings.Set(config.GetKey("focus_messages"), handleFocusMessage); err != nil {
PrintErrorMsg("focus_messages:", err)
}
if err := keyBindings.Set(config.GetKey("focus_input"), handleFocusInput); err != nil {
PrintErrorMsg("focus_input:", err)
}
if err := keyBindings.Set(config.GetKey("focus_contacts"), handleFocusContacts); err != nil {
PrintErrorMsg("focus_contacts:", err)
}
if err := keyBindings.Set(config.GetKey("switch_panels"), handleSwitchPanels); err != nil {
PrintErrorMsg("switch_panels:", err)
}
if err := keyBindings.Set(config.GetKey("command_backlog"), handleCommand("backlog")); err != nil {
PrintErrorMsg("command_backlog:", err)
}
if err := keyBindings.Set(config.GetKey("command_connect"), handleCommand("login")); err != nil {
PrintErrorMsg("command_connect:", err)
}
if err := keyBindings.Set(config.GetKey("command_quit"), handleQuit); err != nil {
PrintErrorMsg("command_quit:", err)
}
if err := keyBindings.Set(config.GetKey("command_help"), handleHelp); err != nil {
PrintErrorMsg("command_help:", err)
}
app.SetInputCapture(keyBindings.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, "[::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, "[-::-]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, "[::b] /backlog[::-] = load more messages for this chat ->[::b]", config.GetKey("command_backlog"), "[::-]")
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
func EnterCommand(key tcell.Key) {
if sndTxt == "" {
return
}
if key == tcell.KeyEsc {
textInput.SetText("")
return
}
switch sndTxt {
}
if sndTxt == "/backlog" {
sessionManager.CommandChannel <- "backlog"
textInput.SetText("")
return
}
if sndTxt == "/connect" {
sessionManager.CommandChannel <- "login"
textInput.SetText("")
return
}
if sndTxt == "/disconnect" {
sessionManager.CommandChannel <- "disconnect"
textInput.SetText("")
return
}
if sndTxt == "/logout" {
sessionManager.CommandChannel <- "logout"
textInput.SetText("")
return
}
if sndTxt == "/help" {
//command
PrintHelp()
textInput.SetText("")
return
}
if sndTxt == "/quit" {
//command
sessionManager.Disconnect()
app.Stop()
return
}
// send message
msg := messages.SendMsg{
Wid: currentReceiver,
Text: sndTxt,
}
sessionManager.SendChannel <- msg
textInput.SetText("")
}
// get the next message id to select (highlighted + offset)
func GetOffsetMsgId(curId string, offset int) string {
if curRegions == nil || len(curRegions) == 0 {
return ""
}
for idx, val := range curRegions {
if val == curId {
arrPos := idx + offset
if len(curRegions) > arrPos && arrPos >= 0 {
return curRegions[arrPos]
}
}
}
if offset > 0 {
return curRegions[0]
} else {
return curRegions[len(curRegions)-1]
}
}
// resets the selection in the textView and scrolls it down
func ResetMsgSelection() {
if len(textView.GetHighlights()) > 0 {
textView.Highlight("")
}
textView.ScrollToEnd()
}
// prints text to the TextView
func PrintText(txt string) {
fmt.Fprintln(textView, txt)
}
// 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 = sessionManager.DownloadMessage(id, true); err == nil {
cmd := exec.Command("jp2a", "--color", path)
var stdout io.ReadCloser
if stdout, err = cmd.StdoutPipe(); err == nil {
if err = cmd.Start(); err == nil {
reader := bufio.NewReader(stdout)
io.Copy(tview.ANSIWriter(textView), reader)
return
}
}
}
PrintError(err)
}
// downloads a specific message attachment
func DownloadMessageId(id string, openIt bool) {
PrintText("[::d]loading..[::-]")
if result, err := sessionManager.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
func NotifyMsg(msg whatsapp.TextMessage) {
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
//fmt.Print("\a")
//err := beeep.Notify(messages.GetIdName(msg.Info.RemoteJid), msg.Text, "")
//if err != nil {
// fmt.Fprintln(textView, "[red]error in notification[-]")
//}
}
}
// sets the current contact, loads text from storage to TextView
func SetDisplayedContact(wid string) {
currentReceiver = wid
textView.Clear()
textView.SetTitle(messages.GetIdName(wid))
textView.SetText(msgStore.GetMessagesString(wid))
sessionManager.DisplayedChannel <- currentReceiver
}
// StartTextReceiver starts the handler for the text messages received
func StartTextReceiver() error {
var wac = GetConnection()
err := LoginWithConnection(wac)
if err != nil {
return fmt.Errorf("%v\n", err)
}
handler = textHandler{}
wac.AddHandler(handler)
sendChannel = make(chan waMsg)
textChannel = make(chan whatsapp.TextMessage)
contactChannel = make(chan whatsapp.Contact)
for {
select {
case msg := <-sendChannel:
SendText(msg.Wid, msg.Text)
case rcvd := <-textChannel:
if msgStore.AddTextMessage(rcvd) {
app.QueueUpdateDraw(LoadContacts)
type UiHandler struct{}
func (u UiHandler) NewMessage(msg string, id string) {
//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)
})
}
func (u UiHandler) NewScreen(screen string, ids []string) {
go app.QueueUpdateDraw(func() {
textView.Clear()
textView.SetText(screen)
curRegions = ids
})
}
// loads the contact data from storage to the TreeView
func (u UiHandler) SetContacts(ids []string) {
go app.QueueUpdateDraw(func() {
contactRoot.ClearChildren()
for _, element := range ids {
node := tview.NewTreeNode(messages.GetIdName(element)).
SetReference(element).
SetSelectable(true)
if strings.Count(element, messages.CONTACTSUFFIX) > 0 {
node.SetColor(config.GetColor("list_contact"))
} else {
node.SetColor(config.GetColor("list_group"))
}
contactRoot.AddChild(node)
if element == currentReceiver {
treeView.SetCurrentNode(node)
}
case contact := <-contactChannel:
messages.SetIdName(contact.Jid, contact.Name)
}
}
fmt.Fprint(textView, "\n"+"closing the receiver")
wac.Disconnect()
return nil
})
}
func SendText(wid string, text string) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: wid,
FromMe: true,
Timestamp: uint64(time.Now().Unix()),
},
Text: text,
}
PrintTextMessage(msg)
//TODO: workaround for error when receiving&sending
connection.RemoveHandlers()
_, err := connection.Send(msg)
msgStore.AddTextMessage(msg)
connection.AddHandler(handler)
if err != nil {
fmt.Fprint(textView, "\nerror sending message: %v", err)
} else {
//fmt.Fprint(textView, "\nSent msg with ID: %v", msgID)
}
func (u UiHandler) PrintError(err error) {
PrintError(err)
}
type textHandler struct{}
// HandleError implements the handler interface for go-whatsapp
func (t textHandler) HandleError(err error) {
// TODO : handle go routine here
fmt.Fprint(textView, "\nerror in textHandler : %v", err)
return
func (u UiHandler) PrintText(msg string) {
PrintText(msg)
}
// HandleTextMessage implements the text message handler interface for go-whatsapp
func (t textHandler) HandleTextMessage(msg whatsapp.TextMessage) {
textChannel <- msg
if msg.Info.RemoteJid != currentReceiver {
//fmt.Print("\a")
return
}
PrintTextMessage(msg)
}
func PrintTextMessage(msg whatsapp.TextMessage) {
fmt.Fprint(textView, messages.GetTextMessageString(&msg))
}
func (t textHandler) HandleImageMessage(message whatsapp.ImageMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[IMAGE] " + message.Caption,
}
t.HandleTextMessage(msg)
}
func (t textHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[DOCUMENT] " + message.Title,
}
t.HandleTextMessage(msg)
}
func (t textHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[VIDEO] " + message.Caption,
}
t.HandleTextMessage(msg)
}
func (t textHandler) HandleAudioMessage(message whatsapp.AudioMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[AUDIO]",
}
t.HandleTextMessage(msg)
}
func (t textHandler) HandleNewContact(contact whatsapp.Contact) {
contactChannel <- contact
}
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
// app.QueueUpdate(func() {
// infoBar.SetText("🔋: " + string(msg.Percentage) + "%")
// })
//}

View File

@@ -5,15 +5,27 @@ import (
"os"
"os/user"
"strings"
"github.com/Rhymen/go-whatsapp"
"github.com/normen/whatscli/config"
)
var contacts map[string]string
var connection *whatsapp.Conn
// loads custom contacts from disk
func LoadContacts() {
contacts = make(map[string]string)
file, err := os.Open(GetHomeDir() + ".whatscli.contacts")
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)
@@ -23,8 +35,9 @@ 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
}
@@ -37,18 +50,47 @@ func SaveContacts() {
return
}
// sets a new name for a whatsapp id
func SetIdName(id string, name string) {
contacts[id] = name
SaveContacts()
}
// gets a pretty name for a whatsapp id
func GetIdName(id string) string {
if _, ok := contacts[id]; ok {
return contacts[id]
}
if val, ok := connection.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 GetIdShort(id string) string {
if val, ok := connection.Store.Contacts[id]; ok {
if val.Short != "" {
return val.Short
} else if val.Name != "" {
return val.Name
} else if val.Notify != "" {
return val.Notify
}
}
if _, ok := contacts[id]; ok {
return contacts[id]
}
return strings.TrimSuffix(id, CONTACTSUFFIX)
}
// gets the OS home dir with a path separator at the end
func GetHomeDir() string {
usr, err := user.Current()
if err != nil {

479
messages/session_manager.go Normal file
View File

@@ -0,0 +1,479 @@
package messages
import (
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/rivo/tview"
"github.com/Rhymen/go-whatsapp"
"github.com/normen/whatscli/config"
"github.com/normen/whatscli/qrcode"
)
// TODO: move message styling and ordering into UI, don't use strings
type UiMessageHandler interface {
NewMessage(string, string)
NewScreen(string, []string)
SetContacts([]string)
PrintError(error)
PrintText(string)
}
type Command struct {
Name string
Params []string
}
type SendMsg struct {
Wid string
Text string
}
const GROUPSUFFIX = "@g.us"
const CONTACTSUFFIX = "@s.whatsapp.net"
type SessionManager struct {
db MessageDatabase
currentReceiver string // currently selected contact for message handling
uiHandler UiMessageHandler
SendChannel chan SendMsg
DisplayedChannel chan string
CommandChannel chan string
TextChannel chan whatsapp.TextMessage
OtherChannel chan interface{}
ContactChannel chan whatsapp.Contact
textView *tview.TextView
}
func (sm *SessionManager) Init(handler UiMessageHandler) {
sm.db = MessageDatabase{}
sm.db.Init()
sm.uiHandler = handler
//TODO: conflate to commandchannel
sm.SendChannel = make(chan SendMsg, 10)
sm.DisplayedChannel = make(chan string, 10)
sm.CommandChannel = make(chan string, 10)
sm.TextChannel = make(chan whatsapp.TextMessage, 10)
sm.OtherChannel = make(chan interface{}, 10)
sm.ContactChannel = make(chan whatsapp.Contact, 10)
}
func (sm *SessionManager) SetCurrentReceiver(id string) {
sm.currentReceiver = id
screen, ids := sm.db.GetMessagesString(id)
sm.uiHandler.NewScreen(screen, ids)
}
// TODO: remove this circular dependeny in favor of a better way
func (sm *SessionManager) SetTextView(tv *tview.TextView) {
sm.textView = tv
}
// gets an existing connection or creates one
func (sm *SessionManager) GetConnection() *whatsapp.Conn {
var wac *whatsapp.Conn
if connection == nil {
wacc, err := whatsapp.NewConn(5 * time.Second)
if err != nil {
return nil
}
wac = wacc
connection = wac
//wac.SetClientVersion(2, 2021, 4)
} else {
wac = connection
}
return wac
}
// Login logs in the user. It ries to see if a session already exists. If not, tries to create a
// new one using qr scanned on the terminal.
func (sm *SessionManager) Login() error {
return sm.LoginWithConnection(sm.GetConnection())
}
// 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 (sm *SessionManager) LoginWithConnection(wac *whatsapp.Conn) error {
if wac != nil && wac.GetConnected() {
wac.Disconnect()
}
//load saved session
session, err := readSession()
if err == nil {
//restore session
session, err = wac.RestoreWithSession(session)
if err != nil {
return fmt.Errorf("restoring failed: %v\n", err)
}
} else {
//no saved session -> regular login
qr := make(chan string)
go func() {
terminal := qrcode.New()
terminal.SetOutput(tview.ANSIWriter(sm.textView))
terminal.Get(<-qr).Print()
}()
session, err = wac.Login(qr)
if err != nil {
return fmt.Errorf("error during login: %v\n", err)
}
}
//save session
err = writeSession(session)
if err != nil {
return fmt.Errorf("error saving session: %v\n", err)
}
//<-time.After(3 * time.Second)
return nil
}
func (sm *SessionManager) Disconnect() error {
wac := sm.GetConnection()
if wac != nil && wac.GetConnected() {
_, err := wac.Disconnect()
return err
}
return nil
}
// Logout logs out the user.
func (ub *SessionManager) Logout() error {
return removeSession()
}
func (sm *SessionManager) ExecCommand(command Command) {
sndTxt := command.Name
switch sndTxt {
default:
case "backlog":
//command
if sm.currentReceiver == "" {
return
}
count := 10
if currentMsgs, ok := sm.db.textMessages[sm.currentReceiver]; ok {
if len(currentMsgs) > 0 {
firstMsg := currentMsgs[0]
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":
//command
sm.Login()
case "disconnect":
//TODO: output error
sm.uiHandler.PrintError(sm.Disconnect())
case "logout":
//command
//TODO: output error
sm.uiHandler.PrintError(sm.Logout())
}
}
// load data for message specified by message id TODO: support types
func (sm *SessionManager) LoadMessageData(wid string) ([]byte, error) {
if msg, ok := sm.db.otherMessages[wid]; ok {
switch v := (*msg).(type) {
default:
case whatsapp.ImageMessage:
return v.Download()
case whatsapp.DocumentMessage:
//return v.Download()
case whatsapp.AudioMessage:
//return v.Download()
case whatsapp.VideoMessage:
//return v.Download()
}
}
return []byte{}, errors.New("This is not an image message")
}
// attempts to download a messages attachments, returns path or error message
func (sm *SessionManager) DownloadMessage(wid string, preview bool) (string, error) {
if msg, ok := sm.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:
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "image/")
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.DocumentMessage:
fileName += v.Info.Id + "." + strings.TrimPrefix(strings.TrimPrefix(v.Type, "application/"), "document/")
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.AudioMessage:
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "audio/")
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
case whatsapp.VideoMessage:
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "video/")
if _, err := os.Stat(fileName); err == nil {
return fileName, err
} else if os.IsNotExist(err) {
if data, err := v.Download(); err == nil {
return saveAttachment(data, fileName)
} else {
return fileName, err
}
}
}
}
return "", errors.New("No attachments found")
}
// create a formatted string with regions based on message ID from a text message
// TODO: move message styling into UI
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
}
out += "[\"\"]"
return out
}
// starts the receiver and message handling thread
// TODO: can't be stopped, can only be called once!
func (sm *SessionManager) StartTextReceiver() error {
var wac = sm.GetConnection()
err := sm.LoginWithConnection(wac)
if err != nil {
return fmt.Errorf("%v\n", err)
}
wac.AddHandler(sm)
for {
select {
case msg := <-sm.SendChannel:
sm.SendText(msg.Wid, msg.Text)
case msg := <-sm.TextChannel:
didNew := sm.db.AddTextMessage(&msg)
if msg.Info.RemoteJid == sm.currentReceiver {
if didNew {
sm.uiHandler.NewMessage(GetTextMessageString(&msg), msg.Info.Id)
} else {
screen, ids := sm.db.GetMessagesString(sm.currentReceiver)
sm.uiHandler.NewScreen(screen, ids)
}
}
sm.uiHandler.SetContacts(sm.db.GetContactIds())
case other := <-sm.OtherChannel:
sm.db.AddOtherMessage(&other)
case contact := <-sm.ContactChannel:
SetIdName(contact.Jid, contact.Name)
case contactId := <-sm.DisplayedChannel:
sm.SetCurrentReceiver(contactId)
case command := <-sm.CommandChannel:
sm.ExecCommand(Command{command, nil})
}
}
fmt.Fprintln(sm.textView, "closing the receiver")
wac.Disconnect()
return nil
}
// sends text to whatsapp id
func (sm SessionManager) SendText(wid string, text string) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: wid,
FromMe: true,
Timestamp: uint64(time.Now().Unix()),
},
Text: text,
}
_, err := sm.GetConnection().Send(msg)
if err != nil {
sm.uiHandler.PrintError(err)
} else {
sm.db.AddTextMessage(&msg)
sm.uiHandler.NewMessage(GetTextMessageString(&msg), msg.Info.Id)
}
}
// handler struct for whatsapp callbacks
// HandleError implements the error handler interface for go-whatsapp
func (sm *SessionManager) HandleError(err error) {
sm.uiHandler.PrintText("[red]go-whatsapp reported an error:[-]")
sm.uiHandler.PrintError(err)
return
}
// HandleTextMessage implements the text message handler interface for go-whatsapp
func (sm *SessionManager) HandleTextMessage(msg whatsapp.TextMessage) {
sm.TextChannel <- msg
}
// methods to convert messages to TextMessage
func (sm *SessionManager) HandleImageMessage(message whatsapp.ImageMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[IMAGE] " + message.Caption,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleDocumentMessage(message whatsapp.DocumentMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[DOCUMENT] " + message.Title,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleVideoMessage(message whatsapp.VideoMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[VIDEO] " + message.Caption,
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
func (sm *SessionManager) HandleAudioMessage(message whatsapp.AudioMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
Id: message.Info.Id,
},
Text: "[AUDIO]",
}
sm.HandleTextMessage(msg)
sm.OtherChannel <- message
}
// 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
}
// handle battery messages
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
// app.QueueUpdate(func() {
// infoBar.SetText("🔋: " + string(msg.Percentage) + "%")
// })
//}
// 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
}
// reads the session file from disk
func readSession() (whatsapp.Session, error) {
session := whatsapp.Session{}
file, err := os.Open(config.GetSessionFilePath())
if err != nil {
// 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)
err = decoder.Decode(&session)
if err != nil {
return session, err
}
return session, nil
}
// saves the session file to disk
func writeSession(session whatsapp.Session) error {
file, err := os.Create(config.GetSessionFilePath())
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(session)
if err != nil {
return err
}
return nil
}
// deletes the session file from disk
func removeSession() error {
return os.Remove(config.GetSessionFilePath())
}

View File

@@ -1,84 +1,111 @@
package messages
import (
"github.com/Rhymen/go-whatsapp"
"github.com/rivo/tview"
"sort"
"strings"
"time"
"github.com/Rhymen/go-whatsapp"
)
const GROUPSUFFIX = "@g.us"
const CONTACTSUFFIX = "@s.whatsapp.net"
type MessageDatabase struct {
textMessages map[string][]whatsapp.TextMessage
latestMessage map[string]uint64
textMessages map[string][]*whatsapp.TextMessage // text messages stored by RemoteJid
messagesById map[string]*whatsapp.TextMessage // text messages stored by message ID
latestMessage map[string]uint64 // last message from RemoteJid
otherMessages map[string]*interface{} // other non-text messages, stored by ID
}
// initialize the database
func (db *MessageDatabase) Init() {
//var this = *db
(*db).textMessages = make(map[string][]whatsapp.TextMessage)
(*db).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)
}
func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool {
// add a text message to the database, stored by RemoteJid
func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool {
//var this = *db
var didNew = false
var wid = msg.Info.RemoteJid
if (*db).textMessages[wid] == nil {
var newArr = []whatsapp.TextMessage{}
(*db).textMessages[wid] = newArr
(*db).latestMessage[wid] = msg.Info.Timestamp
if db.textMessages[wid] == nil {
var newArr = []*whatsapp.TextMessage{}
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)
sort.Slice((*db).textMessages[wid], func(i, j int) bool {
return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp
})
//check if message exists, ignore otherwise
if _, ok := db.messagesById[msg.Info.Id]; !ok {
db.messagesById[msg.Info.Id] = msg
db.textMessages[wid] = append(db.textMessages[wid], 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{}) {
var id = ""
switch v := (*msg).(type) {
default:
case whatsapp.ImageMessage:
id = v.Info.Id
case whatsapp.DocumentMessage:
id = v.Info.Id
case whatsapp.AudioMessage:
id = v.Info.Id
case whatsapp.VideoMessage:
id = v.Info.Id
}
if id != "" {
db.otherMessages[id] = msg
}
}
// get an array of all chat ids
func (db *MessageDatabase) GetContactIds() []string {
//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]]
})
//sort.Strings(keys)
return keys
}
func (db *MessageDatabase) GetMessagesString(wid string) string {
//var this = *db
var out = ""
for _, element := range (*db).textMessages[wid] {
out += GetTextMessageString(&element)
func (db *MessageDatabase) GetMessageInfo(id string) string {
if _, ok := db.otherMessages[id]; ok {
return "[yellow]OtherMessage[-]"
}
out := ""
if msg, ok := db.messagesById[id]; ok {
out += "[yellow]ID: " + msg.Info.Id + "[-]\n"
out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n"
out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n"
out += "[yellow]SenderJid: " + msg.Info.SenderJid + "[-]\n"
out += "[yellow]Participant: " + msg.ContextInfo.Participant + "[-]\n"
out += "[yellow]QuotedMessageID: " + msg.ContextInfo.QuotedMessageID + "[-]\n"
}
return out
}
func GetTextMessageString(msg *whatsapp.TextMessage) string {
out := ""
text := tview.Escape((*msg).Text)
tim := time.Unix(int64((*msg).Info.Timestamp), 0)
if (*msg).Info.FromMe { //msg from me
out += "\n[-::d](" + tim.Format("01-02-06 15:04:05") + ") [blue::b]Me: [-::-]" + text
} else if strings.Contains((*msg).Info.RemoteJid, GROUPSUFFIX) { // group msg
//(*msg).Info.SenderJid
userId := (*msg).Info.SenderJid
//userId := strings.Split(string((*msg).Info.RemoteJid), "-")[0] + CONTACTSUFFIX
out += "\n[-::d](" + tim.Format("01-02-06 15:04:05") + ") [green::b]" + GetIdName(userId) + ": [-::-]" + text
} else { // message from others
out += "\n[-::d](" + tim.Format("01-02-06 15:04:05") + ") [green::b]" + GetIdName((*msg).Info.RemoteJid) + ": [-::-]" + text
// get a string containing all messages for a chat by chat id
func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
//var this = *db
var out = ""
var arr = []string{}
for _, element := range db.textMessages[wid] {
out += GetTextMessageString(element)
out += "\n"
arr = append(arr, element.Info.Id)
}
return out
return out, arr
}

View File

@@ -96,7 +96,7 @@ var (
type QRCodeString string
func (v *QRCodeString) Print() {
fmt.Fprint(outer, *v)
fmt.Fprintln(outer, *v)
}
type qrcodeTerminal struct {

49
release.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
set -e
# get verison from main.go VERSION string
if [ $# -eq 0 ]; then
VERSION=$(cat main.go|grep "VERSION string"| awk -v FS="(\")" '{print $2}')
else
VERSION=$1
fi
echo Releasing $VERSION
WINF=whatscli-$VERSION-windows.zip
LINUXF=whatscli-$VERSION-linux.zip
MACF=whatscli-$VERSION-macos.zip
RASPIF=whatscli-$VERSION-raspberrypi.zip
# build zip files with binaries
GOOS=darwin go build -o whatscli
zip $MACF whatscli
rm whatscli
GOOS=windows go build -o whatscli.exe
zip $WINF whatscli.exe
rm whatscli.exe
GOOS=linux go build -o whatscli
zip $LINUXF whatscli
rm whatscli
GOOS=linux GOARCH=arm GOARM=5 go build -o whatscli
zip $RASPIF whatscli
rm whatscli
# publish to github
git pull
LASTTAG=$(git describe --tags --abbrev=0)
git log $LASTTAG..HEAD --no-decorate --pretty=format:"- %s" --abbrev-commit > changes.txt
vim changes.txt
gh release create $VERSION $LINUXF $MACF $WINF $RASPIF -F changes.txt -t $VERSION
rm changes.txt
rm *.zip
# update homebrew tap
URL="https://github.com/normen/whatscli/archive/$VERSION.tar.gz"
wget $URL
SHASUM=$(shasum -a 256 $VERSION.tar.gz|awk '{print$1}')
rm $VERSION.tar.gz
cd ../../BrewCode/homebrew-tap
sed -i bak "s/sha256 \".*/sha256 \"$SHASUM\"/" Formula/whatscli.rb
sed -i bak "s!url \".*!url \"$URL\"!" Formula/whatscli.rb
rm Formula/whatscli.rbbak
git add -A
git commit -m "update whatscli to $VERSION"
git push

View File

@@ -1,118 +0,0 @@
package main
import (
"encoding/gob"
"fmt"
"github.com/rivo/tview"
"os"
"os/user"
"time"
"github.com/Rhymen/go-whatsapp"
"github.com/normen/whatscli/qrcode"
)
func GetConnection() *whatsapp.Conn {
var wac *whatsapp.Conn
if connection == nil {
wacc, err := whatsapp.NewConn(5 * time.Second)
if err != nil {
return nil
}
wac = wacc
connection = wac
//wac.SetClientVersion(2, 2021, 4)
} else {
wac = connection
}
return wac
}
// Login logs in the user. It ries to see if a session already exists. If not, tries to create a
// new one using qr scanned on the terminal.
func Login() error {
return LoginWithConnection(GetConnection())
}
// 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 wac.Info != nil && wac.Info.Connected {
return nil
}
//load saved session
session, err := readSession()
if err == nil {
//restore session
session, err = wac.RestoreWithSession(session)
if err != nil {
return fmt.Errorf("restoring failed: %v\n", err)
}
} else {
//no saved session -> regular login
qr := make(chan string)
go func() {
terminal := qrcode.New()
terminal.SetOutput(tview.ANSIWriter(textView))
terminal.Get(<-qr).Print()
}()
session, err = wac.Login(qr)
if err != nil {
return fmt.Errorf("error during login: %v\n", err)
}
}
//save session
err = writeSession(session)
if err != nil {
return fmt.Errorf("error saving session: %v\n", err)
}
//<-time.After(3 * time.Second)
//fmt.Fprint(textView, "\nlogin successful")
return nil
}
// Logout logs out the user.
func Logout() error {
return removeSession()
}
func GetHomeDir() string {
usr, err := user.Current()
if err != nil {
}
return usr.HomeDir + string(os.PathSeparator)
}
func readSession() (whatsapp.Session, error) {
session := whatsapp.Session{}
file, err := os.Open(GetHomeDir() + ".whatscli.session")
if err != nil {
return session, err
}
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&session)
if err != nil {
return session, err
}
return session, nil
}
func writeSession(session whatsapp.Session) error {
file, err := os.Create(GetHomeDir() + ".whatscli.session")
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
err = encoder.Encode(session)
if err != nil {
return err
}
return nil
}
func removeSession() error {
return os.Remove(GetHomeDir() + ".whatscli.session")
}