Compare commits
	
		
			68 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f8368e4998 | ||
| 
						 | 
					121a73c312 | ||
| 
						 | 
					86af0d82a4 | ||
| 
						 | 
					b94129fb0e | ||
| 
						 | 
					59a843bb8d | ||
| 
						 | 
					a86aa3eec3 | ||
| 
						 | 
					a20b4e3592 | ||
| 
						 | 
					9e62295188 | ||
| 
						 | 
					2e891d05ca | ||
| 
						 | 
					bfbec54de3 | ||
| 
						 | 
					c677bce14e | ||
| 
						 | 
					6f30efeebe | ||
| 
						 | 
					14a0e74b25 | ||
| 
						 | 
					4951ca24da | ||
| 
						 | 
					1cd6c25b02 | ||
| 
						 | 
					d9a9e7f753 | ||
| 
						 | 
					5388a1b408 | ||
| 
						 | 
					7687af38e1 | ||
| 
						 | 
					9b26641a0c | ||
| 
						 | 
					e5ef667909 | ||
| 
						 | 
					c39479f12b | ||
| 
						 | 
					f1109f6465 | ||
| 
						 | 
					127883701d | ||
| 
						 | 
					5cacc3c5ea | ||
| 
						 | 
					65957ff732 | ||
| 
						 | 
					7a096dbc94 | ||
| 
						 | 
					a7977959b5 | ||
| 
						 | 
					ea92d56426 | ||
| 
						 | 
					3c193d219e | ||
| 
						 | 
					f3a2bd3e88 | ||
| 
						 | 
					f0488851ae | ||
| 
						 | 
					c5276247b8 | ||
| 
						 | 
					27d3a48d98 | ||
| 
						 | 
					9065248d1c | ||
| 
						 | 
					6e0c150e26 | ||
| 
						 | 
					489b23899e | ||
| 
						 | 
					8f50aa02d6 | ||
| 
						 | 
					c3454e734f | ||
| 
						 | 
					2138e671c4 | ||
| 
						 | 
					0b6816f6e3 | ||
| 
						 | 
					b72a5e0cc6 | ||
| 
						 | 
					e4f1851b50 | ||
| 
						 | 
					20f879271c | ||
| 
						 | 
					d60e652d17 | ||
| 
						 | 
					6f0d93e29e | ||
| 
						 | 
					d7f8f0a918 | ||
| 
						 | 
					8d89a2a7f4 | ||
| 
						 | 
					0e4a694d09 | ||
| 
						 | 
					754420e0c7 | ||
| 
						 | 
					ae29e13108 | ||
| 
						 | 
					0e3811de44 | ||
| 
						 | 
					d1c0e870f0 | ||
| 
						 | 
					795d8f7e63 | ||
| 
						 | 
					5008ca46d8 | ||
| 
						 | 
					e03e261d00 | ||
| 
						 | 
					4a119a700f | ||
| 
						 | 
					c1897b475a | ||
| 
						 | 
					1672b42f7e | ||
| 
						 | 
					8a28ea47fd | ||
| 
						 | 
					3450fbc78f | ||
| 
						 | 
					48ad9ce669 | ||
| 
						 | 
					fd676f13cf | ||
| 
						 | 
					63b5f3a604 | ||
| 
						 | 
					b20031ff6a | ||
| 
						 | 
					00516c3191 | ||
| 
						 | 
					76c4010ce2 | ||
| 
						 | 
					0b8d265024 | ||
| 
						 | 
					80825b0dff | 
							
								
								
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal 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']
 | 
			
		||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -8,10 +8,13 @@ A command line interface for whatsapp, based on [go-whatsapp](https://github.com
 | 
			
		||||
 | 
			
		||||
Things that work.
 | 
			
		||||
 | 
			
		||||
- Sending and receiving WhatsApp messages in a command line app
 | 
			
		||||
- 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
 | 
			
		||||
- Allows downloading and opening image/video/audio/document attachments
 | 
			
		||||
- Allows sending documents
 | 
			
		||||
- Allows color customization
 | 
			
		||||
- Supports desktop notifications
 | 
			
		||||
- Binaries for Windows, Mac, Linux and RaspBerry Pi
 | 
			
		||||
 | 
			
		||||
### Caveats
 | 
			
		||||
@@ -19,10 +22,8 @@ Things that work.
 | 
			
		||||
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 shows existing chats
 | 
			
		||||
- Only fetches a few old messages
 | 
			
		||||
- No incoming message notification / count
 | 
			
		||||
- No unread message count
 | 
			
		||||
- 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
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import (
 | 
			
		||||
	"os/user"
 | 
			
		||||
 | 
			
		||||
	"github.com/adrg/xdg"
 | 
			
		||||
	"github.com/gdamore/tcell/v2"
 | 
			
		||||
	"gitlab.com/tslocum/cbind"
 | 
			
		||||
	"gopkg.in/ini.v1"
 | 
			
		||||
)
 | 
			
		||||
@@ -15,39 +14,138 @@ var configFilePath string
 | 
			
		||||
var keyConfig *cbind.Configuration
 | 
			
		||||
var cfg *ini.File
 | 
			
		||||
 | 
			
		||||
type IniFile struct {
 | 
			
		||||
	*General
 | 
			
		||||
	*Keymap
 | 
			
		||||
	*Ui
 | 
			
		||||
	*Colors
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type General struct {
 | 
			
		||||
	DownloadPath        string
 | 
			
		||||
	PreviewPath         string
 | 
			
		||||
	CmdPrefix           string
 | 
			
		||||
	ShowCommand         string
 | 
			
		||||
	EnableNotifications bool
 | 
			
		||||
	NotificationTimeout int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Keymap struct {
 | 
			
		||||
	SwitchPanels    string
 | 
			
		||||
	FocusMessages   string
 | 
			
		||||
	FocusInput      string
 | 
			
		||||
	FocusChats      string
 | 
			
		||||
	CommandBacklog  string
 | 
			
		||||
	CommandRead     string
 | 
			
		||||
	CommandConnect  string
 | 
			
		||||
	CommandQuit     string
 | 
			
		||||
	CommandHelp     string
 | 
			
		||||
	MessageDownload string
 | 
			
		||||
	MessageOpen     string
 | 
			
		||||
	MessageShow     string
 | 
			
		||||
	MessageUrl      string
 | 
			
		||||
	MessageInfo     string
 | 
			
		||||
	MessageRevoke   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Ui struct {
 | 
			
		||||
	ChatSidebarWidth int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Colors struct {
 | 
			
		||||
	Background      string
 | 
			
		||||
	Text            string
 | 
			
		||||
	ListHeader      string
 | 
			
		||||
	ListContact     string
 | 
			
		||||
	ListGroup       string
 | 
			
		||||
	ChatContact     string
 | 
			
		||||
	ChatMe          string
 | 
			
		||||
	Borders         string
 | 
			
		||||
	InputBackground string
 | 
			
		||||
	InputText       string
 | 
			
		||||
	UnreadCount     string
 | 
			
		||||
	Positive        string
 | 
			
		||||
	Negative        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var Config = IniFile{
 | 
			
		||||
	&General{
 | 
			
		||||
		DownloadPath:        GetHomeDir() + "Downloads",
 | 
			
		||||
		PreviewPath:         GetHomeDir() + "Downloads",
 | 
			
		||||
		CmdPrefix:           "/",
 | 
			
		||||
		ShowCommand:         "jp2a --color",
 | 
			
		||||
		EnableNotifications: false,
 | 
			
		||||
		NotificationTimeout: 60,
 | 
			
		||||
	},
 | 
			
		||||
	&Keymap{
 | 
			
		||||
		SwitchPanels:    "Tab",
 | 
			
		||||
		FocusMessages:   "Ctrl+w",
 | 
			
		||||
		FocusInput:      "Ctrl+Space",
 | 
			
		||||
		FocusChats:      "Ctrl+e",
 | 
			
		||||
		CommandBacklog:  "Ctrl+b",
 | 
			
		||||
		CommandRead:     "Ctrl+n",
 | 
			
		||||
		CommandConnect:  "Ctrl+r",
 | 
			
		||||
		CommandQuit:     "Ctrl+q",
 | 
			
		||||
		CommandHelp:     "Ctrl+?",
 | 
			
		||||
		MessageDownload: "d",
 | 
			
		||||
		MessageInfo:     "i",
 | 
			
		||||
		MessageOpen:     "o",
 | 
			
		||||
		MessageUrl:      "u",
 | 
			
		||||
		MessageRevoke:   "r",
 | 
			
		||||
		MessageShow:     "s",
 | 
			
		||||
	},
 | 
			
		||||
	&Ui{
 | 
			
		||||
		ChatSidebarWidth: 30,
 | 
			
		||||
	},
 | 
			
		||||
	&Colors{
 | 
			
		||||
		Background:      "black",
 | 
			
		||||
		Text:            "white",
 | 
			
		||||
		ListHeader:      "yellow",
 | 
			
		||||
		ListContact:     "green",
 | 
			
		||||
		ListGroup:       "blue",
 | 
			
		||||
		ChatContact:     "green",
 | 
			
		||||
		ChatMe:          "blue",
 | 
			
		||||
		Borders:         "white",
 | 
			
		||||
		InputBackground: "blue",
 | 
			
		||||
		InputText:       "white",
 | 
			
		||||
		UnreadCount:     "yellow",
 | 
			
		||||
		Positive:        "green",
 | 
			
		||||
		Negative:        "red",
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func InitConfig() {
 | 
			
		||||
	var err error
 | 
			
		||||
	if configFilePath, err = xdg.ConfigFile("whatscli/whatscli.config"); err == nil {
 | 
			
		||||
		// add any new values
 | 
			
		||||
		var cfg *ini.File
 | 
			
		||||
		if cfg, err = ini.Load(configFilePath); err == nil {
 | 
			
		||||
			//TODO: check config for new parameters
 | 
			
		||||
			cfg.NameMapper = ini.TitleUnderscore
 | 
			
		||||
			cfg.ValueMapper = os.ExpandEnv
 | 
			
		||||
			if section, err := cfg.GetSection("general"); err == nil {
 | 
			
		||||
				section.MapTo(&Config.General)
 | 
			
		||||
			}
 | 
			
		||||
			if section, err := cfg.GetSection("keymap"); err == nil {
 | 
			
		||||
				section.MapTo(&Config.Keymap)
 | 
			
		||||
			}
 | 
			
		||||
			if section, err := cfg.GetSection("ui"); err == nil {
 | 
			
		||||
				section.MapTo(&Config.Ui)
 | 
			
		||||
			}
 | 
			
		||||
			if section, err := cfg.GetSection("colors"); err == nil {
 | 
			
		||||
				section.MapTo(&Config.Colors)
 | 
			
		||||
			}
 | 
			
		||||
			newCfg := ini.Empty()
 | 
			
		||||
			if err = ini.ReflectFromWithMapper(newCfg, &Config, ini.TitleUnderscore); err == nil {
 | 
			
		||||
				//TODO: only save if changes
 | 
			
		||||
				err = newCfg.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_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)
 | 
			
		||||
			cfg.NameMapper = ini.TitleUnderscore
 | 
			
		||||
			cfg.ValueMapper = os.ExpandEnv
 | 
			
		||||
			if err = ini.ReflectFromWithMapper(cfg, &Config, ini.TitleUnderscore); err == nil {
 | 
			
		||||
				err = cfg.SaveTo(configFilePath)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -66,59 +164,6 @@ func GetSessionFilePath() string {
 | 
			
		||||
	return GetHomeDir() + ".whatscli.session"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetContactsFilePath() string {
 | 
			
		||||
	if sessionFilePath, err := xdg.ConfigFile("whatscli/contacts"); err == nil {
 | 
			
		||||
		return sessionFilePath
 | 
			
		||||
	}
 | 
			
		||||
	return GetHomeDir() + ".whatscli.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()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
								
							@@ -5,7 +5,9 @@ go 1.15
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Rhymen/go-whatsapp v0.1.1
 | 
			
		||||
	github.com/adrg/xdg v0.2.3
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.1.2
 | 
			
		||||
	github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
 | 
			
		||||
	github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28
 | 
			
		||||
	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
 | 
			
		||||
@@ -14,10 +16,11 @@ 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
 | 
			
		||||
	gitlab.com/tslocum/cbind v0.1.4
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2 // indirect
 | 
			
		||||
	golang.org/x/text v0.3.4 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.25.0 // indirect
 | 
			
		||||
	gopkg.in/ini.v1 v1.62.0
 | 
			
		||||
	mvdan.cc/xurls/v2 v2.2.0
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								go.sum
									
									
									
									
									
								
							@@ -15,13 +15,20 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 | 
			
		||||
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/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
 | 
			
		||||
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.0/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/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 h1:M2Zt3G2w6Q57GZndOYk42p7RvMeO8izO8yKTfIxGqxA=
 | 
			
		||||
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8=
 | 
			
		||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
 | 
			
		||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
 | 
			
		||||
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
 | 
			
		||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
			
		||||
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=
 | 
			
		||||
@@ -40,13 +47,18 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 | 
			
		||||
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-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
 | 
			
		||||
github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
 | 
			
		||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 | 
			
		||||
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/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
 | 
			
		||||
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
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/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 | 
			
		||||
@@ -58,6 +70,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 | 
			
		||||
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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
 | 
			
		||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
 | 
			
		||||
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=
 | 
			
		||||
@@ -67,6 +81,7 @@ github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893 h1:24As98PZlIdjZn6V4wUu
 | 
			
		||||
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/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
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=
 | 
			
		||||
@@ -74,12 +89,16 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE
 | 
			
		||||
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=
 | 
			
		||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
 | 
			
		||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
 | 
			
		||||
gitlab.com/tslocum/cbind v0.1.4 h1:cbZXPPcieXspk8cShoT6efz7HAT8yMNQcofYWNizis4=
 | 
			
		||||
gitlab.com/tslocum/cbind v0.1.4/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=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
			
		||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 | 
			
		||||
@@ -102,12 +121,13 @@ 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-20200302150141-5c8b2ff67527/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/sys v0.0.0-20201126144705-a4b67b81d3d2 h1:WFCmm2Hi9I2gYf1kv7LQ8ajKA5x9heC2v9xuUKwvf68=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201126144705-a4b67b81d3d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
@@ -139,8 +159,13 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 | 
			
		||||
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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
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=
 | 
			
		||||
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
 | 
			
		||||
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										693
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										693
									
								
								main.go
									
									
									
									
									
								
							@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
	"github.com/gdamore/tcell/v2"
 | 
			
		||||
	"github.com/normen/whatscli/config"
 | 
			
		||||
	"github.com/normen/whatscli/messages"
 | 
			
		||||
@@ -17,57 +16,53 @@ import (
 | 
			
		||||
	"gitlab.com/tslocum/cbind"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type waMsg struct {
 | 
			
		||||
	Wid  string
 | 
			
		||||
	Text string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var VERSION string = "v0.7.0"
 | 
			
		||||
 | 
			
		||||
var sendChannel chan waMsg
 | 
			
		||||
var textChannel chan whatsapp.TextMessage
 | 
			
		||||
var otherChannel chan interface{}
 | 
			
		||||
var contactChannel chan whatsapp.Contact
 | 
			
		||||
var VERSION string = "v0.9.8"
 | 
			
		||||
 | 
			
		||||
var sndTxt string = ""
 | 
			
		||||
var currentReceiver string = ""
 | 
			
		||||
var curRegions []string
 | 
			
		||||
var currentReceiver messages.Chat = messages.Chat{}
 | 
			
		||||
var curRegions []messages.Message
 | 
			
		||||
 | 
			
		||||
var textView *tview.TextView
 | 
			
		||||
var treeView *tview.TreeView
 | 
			
		||||
var textInput *tview.InputField
 | 
			
		||||
var topBar *tview.TextView
 | 
			
		||||
var infoBar *tview.TextView
 | 
			
		||||
 | 
			
		||||
//var infoBar *tview.TextView
 | 
			
		||||
var msgStore messages.MessageDatabase
 | 
			
		||||
var keysApp *cbind.Configuration
 | 
			
		||||
 | 
			
		||||
var contactRoot *tview.TreeNode
 | 
			
		||||
var handler textHandler
 | 
			
		||||
var chatRoot *tview.TreeNode
 | 
			
		||||
var app *tview.Application
 | 
			
		||||
 | 
			
		||||
var sessionManager *messages.SessionManager
 | 
			
		||||
 | 
			
		||||
var keyBindings *cbind.Configuration
 | 
			
		||||
 | 
			
		||||
var uiHandler messages.UiMessageHandler
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	config.InitConfig()
 | 
			
		||||
	msgStore = messages.MessageDatabase{}
 | 
			
		||||
	msgStore.Init()
 | 
			
		||||
	messages.LoadContacts()
 | 
			
		||||
	uiHandler = UiHandler{}
 | 
			
		||||
	sessionManager = &messages.SessionManager{}
 | 
			
		||||
	sessionManager.Init(uiHandler)
 | 
			
		||||
 | 
			
		||||
	app = tview.NewApplication()
 | 
			
		||||
 | 
			
		||||
	sideBarWidth := config.GetIntSetting("ui", "contact_sidebar_width")
 | 
			
		||||
	sideBarWidth := config.Config.Ui.ChatSidebarWidth
 | 
			
		||||
	gridLayout := tview.NewGrid()
 | 
			
		||||
	gridLayout.SetRows(1, 0, 1)
 | 
			
		||||
	gridLayout.SetColumns(sideBarWidth, 0, sideBarWidth)
 | 
			
		||||
	gridLayout.SetBorders(true)
 | 
			
		||||
	gridLayout.SetBackgroundColor(config.GetColor("background"))
 | 
			
		||||
	gridLayout.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
 | 
			
		||||
	gridLayout.SetBordersColor(tcell.ColorNames[config.Config.Colors.Borders])
 | 
			
		||||
 | 
			
		||||
	cmdPrefix := config.Config.General.CmdPrefix
 | 
			
		||||
	topBar = tview.NewTextView()
 | 
			
		||||
	topBar.SetDynamicColors(true)
 | 
			
		||||
	topBar.SetScrollable(false)
 | 
			
		||||
	topBar.SetText("[::b] WhatsCLI " + VERSION + "  [-::d]Type /help for help")
 | 
			
		||||
	topBar.SetBackgroundColor(config.GetColor("background"))
 | 
			
		||||
	topBar.SetText("[::b] WhatsCLI " + VERSION + "  [-::d]Type " + cmdPrefix + "help or press " + config.Config.Keymap.CommandHelp + " for help")
 | 
			
		||||
	topBar.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
 | 
			
		||||
	UpdateStatusBar(messages.SessionStatus{})
 | 
			
		||||
 | 
			
		||||
	//infoBar = tview.NewTextView()
 | 
			
		||||
	//infoBar.SetDynamicColors(true)
 | 
			
		||||
	//infoBar.SetText("🔋: ??%")
 | 
			
		||||
	infoBar = tview.NewTextView()
 | 
			
		||||
	infoBar.SetDynamicColors(true)
 | 
			
		||||
 | 
			
		||||
	textView = tview.NewTextView().
 | 
			
		||||
		SetDynamicColors(true).
 | 
			
		||||
@@ -76,16 +71,15 @@ func main() {
 | 
			
		||||
		SetChangedFunc(func() {
 | 
			
		||||
			app.Draw()
 | 
			
		||||
		})
 | 
			
		||||
	textView.SetBackgroundColor(config.GetColor("background"))
 | 
			
		||||
	textView.SetTextColor(config.GetColor("text"))
 | 
			
		||||
	textView.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
 | 
			
		||||
	textView.SetTextColor(tcell.ColorNames[config.Config.Colors.Text])
 | 
			
		||||
 | 
			
		||||
	// TODO: add better way
 | 
			
		||||
	messages.SetTextView(textView)
 | 
			
		||||
	PrintHelp()
 | 
			
		||||
 | 
			
		||||
	textInput = tview.NewInputField()
 | 
			
		||||
	textInput.SetBackgroundColor(config.GetColor("background"))
 | 
			
		||||
	textView.SetTextColor(config.GetColor("text"))
 | 
			
		||||
	textInput.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
 | 
			
		||||
	textInput.SetFieldBackgroundColor(tcell.ColorNames[config.Config.Colors.InputBackground])
 | 
			
		||||
	textInput.SetFieldTextColor(tcell.ColorNames[config.Config.Colors.InputText])
 | 
			
		||||
	textInput.SetChangedFunc(func(change string) {
 | 
			
		||||
		sndTxt = change
 | 
			
		||||
	})
 | 
			
		||||
@@ -119,67 +113,48 @@ func main() {
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	gridLayout.AddItem(topBar, 0, 0, 1, 4, 0, 0, false)
 | 
			
		||||
	//gridLayout.AddItem(infoBar, 0, 0, 1, 1, 0, 0, false)
 | 
			
		||||
	gridLayout.AddItem(MakeTree(), 1, 0, 2, 1, 0, 0, false)
 | 
			
		||||
	gridLayout.AddItem(infoBar, 2, 0, 1, 1, 0, 0, false)
 | 
			
		||||
	gridLayout.AddItem(MakeTree(), 1, 0, 1, 1, 0, 0, false)
 | 
			
		||||
	gridLayout.AddItem(textView, 1, 1, 1, 3, 0, 0, false)
 | 
			
		||||
	gridLayout.AddItem(textInput, 2, 1, 1, 3, 0, 0, false)
 | 
			
		||||
 | 
			
		||||
	app.SetRoot(gridLayout, true)
 | 
			
		||||
	app.EnableMouse(true)
 | 
			
		||||
	app.SetFocus(textInput)
 | 
			
		||||
	go func() {
 | 
			
		||||
		if err := StartTextReceiver(); err != nil {
 | 
			
		||||
			PrintError(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	if err := sessionManager.StartManager(); err != nil {
 | 
			
		||||
		PrintError(err)
 | 
			
		||||
	}
 | 
			
		||||
	LoadShortcuts()
 | 
			
		||||
	app.Run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// creates the TreeView for contacts
 | 
			
		||||
// creates the TreeView for chats
 | 
			
		||||
func MakeTree() *tview.TreeView {
 | 
			
		||||
	rootDir := "Contacts"
 | 
			
		||||
	contactRoot = tview.NewTreeNode(rootDir).
 | 
			
		||||
		SetColor(config.GetColor("list_header"))
 | 
			
		||||
	rootDir := "Chats"
 | 
			
		||||
	chatRoot = tview.NewTreeNode(rootDir).
 | 
			
		||||
		SetColor(tcell.ColorNames[config.Config.Colors.ListHeader])
 | 
			
		||||
	treeView = tview.NewTreeView().
 | 
			
		||||
		SetRoot(contactRoot).
 | 
			
		||||
		SetCurrentNode(contactRoot)
 | 
			
		||||
	treeView.SetBackgroundColor(config.GetColor("background"))
 | 
			
		||||
		SetRoot(chatRoot).
 | 
			
		||||
		SetCurrentNode(chatRoot)
 | 
			
		||||
	treeView.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
 | 
			
		||||
 | 
			
		||||
	// If a contact was selected, open it.
 | 
			
		||||
	// If a chat was selected, open it.
 | 
			
		||||
	treeView.SetChangedFunc(func(node *tview.TreeNode) {
 | 
			
		||||
		reference := node.GetReference()
 | 
			
		||||
		if reference == nil {
 | 
			
		||||
			SetDisplayedChat(messages.Chat{"", false, "", 0, 0})
 | 
			
		||||
			return // Selecting the root node does nothing.
 | 
			
		||||
		}
 | 
			
		||||
		children := node.GetChildren()
 | 
			
		||||
		if len(children) == 0 {
 | 
			
		||||
			// Load and show files in this directory.
 | 
			
		||||
			recv := reference.(string)
 | 
			
		||||
			SetDisplayedContact(recv)
 | 
			
		||||
			recv := reference.(messages.Chat)
 | 
			
		||||
			SetDisplayedChat(recv)
 | 
			
		||||
		} else {
 | 
			
		||||
			// Collapse if visible, expand if collapsed.
 | 
			
		||||
			node.SetExpanded(!node.IsExpanded())
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	treeView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
		if event.Key() == tcell.KeyTab {
 | 
			
		||||
			app.SetFocus(textInput)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if event.Key() == tcell.KeyCtrlSpace {
 | 
			
		||||
			app.SetFocus(textInput)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if event.Key() == tcell.KeyCtrlW {
 | 
			
		||||
			app.SetFocus(textView)
 | 
			
		||||
			if curRegions != nil && len(curRegions) > 0 {
 | 
			
		||||
				textView.Highlight(curRegions[len(curRegions)-1])
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return event
 | 
			
		||||
	})
 | 
			
		||||
	return treeView
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -187,7 +162,7 @@ func handleFocusMessage(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	if !textView.HasFocus() {
 | 
			
		||||
		app.SetFocus(textView)
 | 
			
		||||
		if curRegions != nil && len(curRegions) > 0 {
 | 
			
		||||
			textView.Highlight(curRegions[len(curRegions)-1])
 | 
			
		||||
			textView.Highlight(curRegions[len(curRegions)-1].Id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -219,13 +194,15 @@ func handleSwitchPanels(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleConnect(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	msgStore.Init()
 | 
			
		||||
	messages.Login()
 | 
			
		||||
	return nil
 | 
			
		||||
func handleCommand(command string) func(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	return func(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
		sessionManager.CommandChannel <- messages.Command{command, nil}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleQuit(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	sessionManager.CommandChannel <- messages.Command{"disconnect", nil}
 | 
			
		||||
	app.Stop()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -235,44 +212,16 @@ func handleHelp(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	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)
 | 
			
		||||
func handleMessageCommand(command string) func(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	return func(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
		hls := textView.GetHighlights()
 | 
			
		||||
		if len(hls) > 0 {
 | 
			
		||||
			sessionManager.CommandChannel <- messages.Command{command, []string{hls[0]}}
 | 
			
		||||
			ResetMsgSelection()
 | 
			
		||||
			app.SetFocus(textInput)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
@@ -286,7 +235,7 @@ func handleMessagesUp(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
			textView.Highlight(newId)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		textView.Highlight(curRegions[len(curRegions)-1])
 | 
			
		||||
		textView.Highlight(curRegions[len(curRegions)-1].Id)
 | 
			
		||||
	}
 | 
			
		||||
	textView.ScrollToHighlight()
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -303,7 +252,7 @@ func handleMessagesDown(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
			textView.Highlight(newId)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		textView.Highlight(curRegions[0])
 | 
			
		||||
		textView.Highlight(curRegions[0].Id)
 | 
			
		||||
	}
 | 
			
		||||
	textView.ScrollToHighlight()
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -313,7 +262,7 @@ func handleMessagesLast(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	if curRegions == nil || len(curRegions) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	textView.Highlight(curRegions[len(curRegions)-1])
 | 
			
		||||
	textView.Highlight(curRegions[len(curRegions)-1].Id)
 | 
			
		||||
	textView.ScrollToHighlight()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -322,7 +271,7 @@ func handleMessagesFirst(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	if curRegions == nil || len(curRegions) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	textView.Highlight(curRegions[0])
 | 
			
		||||
	textView.Highlight(curRegions[0].Id)
 | 
			
		||||
	textView.ScrollToHighlight()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -336,43 +285,56 @@ func handleExitMessages(ev *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// load the key map
 | 
			
		||||
func LoadShortcuts() {
 | 
			
		||||
	keysApp = cbind.NewConfiguration()
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("focus_messages"), handleFocusMessage); err != nil {
 | 
			
		||||
	keyBindings = cbind.NewConfiguration()
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.FocusMessages, handleFocusMessage); err != nil {
 | 
			
		||||
		PrintErrorMsg("focus_messages:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("focus_input"), handleFocusInput); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.FocusInput, handleFocusInput); err != nil {
 | 
			
		||||
		PrintErrorMsg("focus_input:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("focus_contacts"), handleFocusContacts); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.FocusChats, handleFocusContacts); err != nil {
 | 
			
		||||
		PrintErrorMsg("focus_contacts:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("switch_panels"), handleSwitchPanels); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.SwitchPanels, handleSwitchPanels); err != nil {
 | 
			
		||||
		PrintErrorMsg("switch_panels:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("command_connect"), handleConnect); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.CommandRead, handleCommand("read")); err != nil {
 | 
			
		||||
		PrintErrorMsg("command_read:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.CommandBacklog, handleCommand("backlog")); err != nil {
 | 
			
		||||
		PrintErrorMsg("command_backlog:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.CommandConnect, handleCommand("login")); err != nil {
 | 
			
		||||
		PrintErrorMsg("command_connect:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("command_quit"), handleQuit); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.CommandQuit, handleQuit); err != nil {
 | 
			
		||||
		PrintErrorMsg("command_quit:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysApp.Set(config.GetKey("command_help"), handleHelp); err != nil {
 | 
			
		||||
	if err := keyBindings.Set(config.Config.Keymap.CommandHelp, handleHelp); err != nil {
 | 
			
		||||
		PrintErrorMsg("command_help:", err)
 | 
			
		||||
	}
 | 
			
		||||
	app.SetInputCapture(keysApp.Capture)
 | 
			
		||||
	app.SetInputCapture(keyBindings.Capture)
 | 
			
		||||
	keysMessages := cbind.NewConfiguration()
 | 
			
		||||
	if err := keysMessages.Set(config.GetKey("message_download"), handleDownload); err != nil {
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageDownload, handleMessageCommand("download")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_download:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysMessages.Set(config.GetKey("message_open"), handleOpen); err != nil {
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageOpen, handleMessageCommand("open")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_open:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysMessages.Set(config.GetKey("message_show"), handleShow); err != nil {
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageShow, handleMessageCommand("show")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_show:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysMessages.Set(config.GetKey("message_info"), handleInfo); err != nil {
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageUrl, handleMessageCommand("url")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_url:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageInfo, handleMessageCommand("info")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_info:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := keysMessages.Set(config.Config.Keymap.MessageRevoke, handleMessageCommand("revoke")); err != nil {
 | 
			
		||||
		PrintErrorMsg("message_revoke:", err)
 | 
			
		||||
	}
 | 
			
		||||
	keysMessages.SetKey(tcell.ModNone, tcell.KeyEscape, handleExitMessages)
 | 
			
		||||
	keysMessages.SetKey(tcell.ModNone, tcell.KeyUp, handleMessagesUp)
 | 
			
		||||
	keysMessages.SetKey(tcell.ModNone, tcell.KeyDown, handleMessagesDown)
 | 
			
		||||
@@ -385,33 +347,49 @@ func LoadShortcuts() {
 | 
			
		||||
 | 
			
		||||
// prints help to chat view
 | 
			
		||||
func PrintHelp() {
 | 
			
		||||
	cmdPrefix := config.Config.General.CmdPrefix
 | 
			
		||||
	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, "[-::-]Chat window focused:[-::-]")
 | 
			
		||||
	fmt.Fprintln(textView, "Global")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] Up/Down[::-] = Scroll history/chats")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.SwitchPanels, "[::-] = Switch input/chats")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.FocusMessages, "[::-] = Focus message panel")
 | 
			
		||||
	fmt.Fprintln(textView, "")
 | 
			
		||||
	fmt.Fprintln(textView, "[-::-]Message panel[-::-]")
 | 
			
		||||
	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, "[::b]", config.Config.Keymap.MessageDownload, "[::-] = Download attachment")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageOpen, "[::-] = Download & open attachment")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageShow, "[::-] = Download & show image using", config.Config.General.ShowCommand)
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageUrl, "[::-] = Find URL in message and open it")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageRevoke, "[::-] = Revoke message")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b]", config.Config.Keymap.MessageInfo, "[::-] = Info about message")
 | 
			
		||||
	fmt.Fprintln(textView, "")
 | 
			
		||||
	fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] /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())
 | 
			
		||||
	fmt.Fprintln(textView, "[-::-]Global[-::-]")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"connect [::-]or[::b]", config.Config.Keymap.CommandConnect, "[::-] = (Re)Connect to server")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"disconnect[::-]  = Close the connection")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"logout[::-]  = Remove login data from computer")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"quit [::-]or[::b]", config.Config.Keymap.CommandQuit, "[::-] = Exit app")
 | 
			
		||||
	fmt.Fprintln(textView, "")
 | 
			
		||||
	fmt.Fprintln(textView, "[-::-]Chat[-::-]")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"backlog [::-]or[::b]", config.Config.Keymap.CommandBacklog, "[::-] = load next 10 previous messages")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"read [::-]or[::b]", config.Config.Keymap.CommandRead, "[::-] = mark new messages in chat as read")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"upload[::-] /path/to/file  = Upload any file as document")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendimage[::-] /path/to/file  = Send image message")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendvideo[::-] /path/to/file  = Send video message")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"sendaudio[::-] /path/to/file  = Send audio message")
 | 
			
		||||
	fmt.Fprintln(textView, "[::b] "+cmdPrefix+"leave[::-]  = Leave group")
 | 
			
		||||
	fmt.Fprintln(textView, "")
 | 
			
		||||
	fmt.Fprintln(textView, "Configuration:")
 | 
			
		||||
	fmt.Fprintln(textView, " ->", config.GetConfigFilePath())
 | 
			
		||||
	fmt.Fprintln(textView, "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// called when text is entered by the user
 | 
			
		||||
// TODO: parse and map commands automatically
 | 
			
		||||
func EnterCommand(key tcell.Key) {
 | 
			
		||||
	if sndTxt == "" {
 | 
			
		||||
		return
 | 
			
		||||
@@ -420,82 +398,37 @@ func EnterCommand(key tcell.Key) {
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if sndTxt == "/connect" {
 | 
			
		||||
		//command
 | 
			
		||||
		msgStore.Init()
 | 
			
		||||
		messages.Login()
 | 
			
		||||
		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()
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if sndTxt == "/help" {
 | 
			
		||||
	cmdPrefix := config.Config.General.CmdPrefix
 | 
			
		||||
	if sndTxt == cmdPrefix+"help" {
 | 
			
		||||
		//command
 | 
			
		||||
		PrintHelp()
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if sndTxt == "/quit" {
 | 
			
		||||
	if sndTxt == cmdPrefix+"quit" {
 | 
			
		||||
		//command
 | 
			
		||||
		sessionManager.CommandChannel <- messages.Command{"disconnect", nil}
 | 
			
		||||
		app.Stop()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if sndTxt == "/keys" {
 | 
			
		||||
		//command
 | 
			
		||||
		//config.PrintKeys(textView)
 | 
			
		||||
	if strings.HasPrefix(sndTxt, cmdPrefix) {
 | 
			
		||||
		cmd := strings.TrimPrefix(sndTxt, cmdPrefix)
 | 
			
		||||
		var params []string
 | 
			
		||||
		if strings.Index(cmd, " ") >= 0 {
 | 
			
		||||
			cmdParts := strings.Split(cmd, " ")
 | 
			
		||||
			cmd = cmdParts[0]
 | 
			
		||||
			params = cmdParts[1:]
 | 
			
		||||
		}
 | 
			
		||||
		sessionManager.CommandChannel <- messages.Command{cmd, params}
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Index(sndTxt, "/addname ") == 0 {
 | 
			
		||||
		//command
 | 
			
		||||
		parts := strings.Split(sndTxt, " ")
 | 
			
		||||
		if len(parts) < 3 {
 | 
			
		||||
			fmt.Fprintln(textView, "Use /addname 1234567 NewName")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		contact := whatsapp.Contact{
 | 
			
		||||
			Jid:  parts[1] + messages.CONTACTSUFFIX,
 | 
			
		||||
			Name: strings.TrimPrefix(sndTxt, "/addname "+parts[1]+" "),
 | 
			
		||||
		}
 | 
			
		||||
		contactChannel <- contact
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	// no command, send as message
 | 
			
		||||
	msg := messages.Command{
 | 
			
		||||
		Name:   "send",
 | 
			
		||||
		Params: []string{currentReceiver.Id, sndTxt},
 | 
			
		||||
	}
 | 
			
		||||
	if currentReceiver == "" {
 | 
			
		||||
		fmt.Fprintln(textView, "[red]no contact selected[-]")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Index(sndTxt, "/name ") == 0 {
 | 
			
		||||
		//command
 | 
			
		||||
		contact := whatsapp.Contact{
 | 
			
		||||
			Jid:  currentReceiver,
 | 
			
		||||
			Name: strings.TrimPrefix(sndTxt, "/name "),
 | 
			
		||||
		}
 | 
			
		||||
		contactChannel <- contact
 | 
			
		||||
		textInput.SetText("")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// send message
 | 
			
		||||
	msg := waMsg{
 | 
			
		||||
		Wid:  currentReceiver,
 | 
			
		||||
		Text: sndTxt,
 | 
			
		||||
	}
 | 
			
		||||
	sendChannel <- msg
 | 
			
		||||
	sessionManager.CommandChannel <- msg
 | 
			
		||||
	textInput.SetText("")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -505,17 +438,17 @@ func GetOffsetMsgId(curId string, offset int) string {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	for idx, val := range curRegions {
 | 
			
		||||
		if val == curId {
 | 
			
		||||
		if val.Id == curId {
 | 
			
		||||
			arrPos := idx + offset
 | 
			
		||||
			if len(curRegions) > arrPos && arrPos >= 0 {
 | 
			
		||||
				return curRegions[arrPos]
 | 
			
		||||
				return curRegions[arrPos].Id
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if offset > 0 {
 | 
			
		||||
		return curRegions[0]
 | 
			
		||||
		return curRegions[0].Id
 | 
			
		||||
	} else {
 | 
			
		||||
		return curRegions[len(curRegions)-1]
 | 
			
		||||
		return curRegions[len(curRegions)-1].Id
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -527,11 +460,6 @@ func ResetMsgSelection() {
 | 
			
		||||
	textView.ScrollToEnd()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prints a text message to the TextView
 | 
			
		||||
func PrintTextMessage(msg whatsapp.TextMessage) {
 | 
			
		||||
	fmt.Fprintln(textView, messages.GetTextMessageString(&msg))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prints text to the TextView
 | 
			
		||||
func PrintText(txt string) {
 | 
			
		||||
	fmt.Fprintln(textView, txt)
 | 
			
		||||
@@ -542,7 +470,7 @@ func PrintError(err error) {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(textView, "[red]", err.Error(), "[-]")
 | 
			
		||||
	fmt.Fprintln(textView, "["+config.Config.Colors.Negative+"]", err.Error(), "[-]")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prints an error to the TextView
 | 
			
		||||
@@ -550,230 +478,177 @@ func PrintErrorMsg(text string, err error) {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(textView, "[red]", text, err.Error(), "[-]")
 | 
			
		||||
	fmt.Fprintln(textView, "["+config.Config.Colors.Negative+"]", text, err.Error(), "[-]")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prints an image attachment to the TextView (by message id)
 | 
			
		||||
func PrintImage(id string) {
 | 
			
		||||
func PrintImage(path string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var path string
 | 
			
		||||
	PrintText("[::d]loading..[::-]")
 | 
			
		||||
	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 {
 | 
			
		||||
			if err = cmd.Start(); err == nil {
 | 
			
		||||
				reader := bufio.NewReader(stdout)
 | 
			
		||||
				io.Copy(tview.ANSIWriter(textView), reader)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
	cmdParts := strings.Split(config.Config.General.ShowCommand, " ")
 | 
			
		||||
	cmdParts = append(cmdParts, path)
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	size := len(cmdParts)
 | 
			
		||||
	if size > 1 {
 | 
			
		||||
		cmd = exec.Command(cmdParts[0], cmdParts[1:]...)
 | 
			
		||||
	} else if size > 0 {
 | 
			
		||||
		cmd = exec.Command(cmdParts[0])
 | 
			
		||||
	}
 | 
			
		||||
	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 := msgStore.DownloadMessage(id, openIt); err == nil {
 | 
			
		||||
		PrintText("[::d]Downloaded as [yellow]" + result + "[-::-]")
 | 
			
		||||
		if openIt {
 | 
			
		||||
			open.Run(result)
 | 
			
		||||
		}
 | 
			
		||||
// updates the status bar
 | 
			
		||||
func UpdateStatusBar(statusInfo messages.SessionStatus) {
 | 
			
		||||
	out := " "
 | 
			
		||||
	if statusInfo.Connected {
 | 
			
		||||
		out += "[" + config.Config.Colors.Positive + "]online[-]"
 | 
			
		||||
	} else {
 | 
			
		||||
		PrintError(err)
 | 
			
		||||
		out += "[" + config.Config.Colors.Negative + "]offline[-]"
 | 
			
		||||
	}
 | 
			
		||||
	out += " "
 | 
			
		||||
	out += "[::d] ("
 | 
			
		||||
	out += fmt.Sprint(statusInfo.BatteryCharge)
 | 
			
		||||
	out += "%"
 | 
			
		||||
	if statusInfo.BatteryLoading {
 | 
			
		||||
		out += " [" + config.Config.Colors.Positive + "]L[-]"
 | 
			
		||||
	} else {
 | 
			
		||||
		out += " [" + config.Config.Colors.Negative + "]l[-]"
 | 
			
		||||
	}
 | 
			
		||||
	if statusInfo.BatteryPowersave {
 | 
			
		||||
		out += " [" + config.Config.Colors.Negative + "]S[-]"
 | 
			
		||||
	} else {
 | 
			
		||||
		out += " [" + config.Config.Colors.Positive + "]s[-]"
 | 
			
		||||
	}
 | 
			
		||||
	out += ")[::-] "
 | 
			
		||||
	out += statusInfo.LastSeen
 | 
			
		||||
	go app.QueueUpdateDraw(func() {
 | 
			
		||||
		infoBar.SetText(out)
 | 
			
		||||
	})
 | 
			
		||||
	//infoBar.SetText("🔋: ??%")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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[-]")
 | 
			
		||||
		//}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loads the contact data from storage to the 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(config.GetColor("list_contact"))
 | 
			
		||||
		} else {
 | 
			
		||||
			node.SetColor(config.GetColor("list_group"))
 | 
			
		||||
		}
 | 
			
		||||
		contactRoot.AddChild(node)
 | 
			
		||||
		if element == currentReceiver {
 | 
			
		||||
			treeView.SetCurrentNode(node)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sets the current contact, loads text from storage to TextView
 | 
			
		||||
func SetDisplayedContact(wid string) {
 | 
			
		||||
// sets the current chat, loads text from storage to TextView
 | 
			
		||||
func SetDisplayedChat(wid messages.Chat) {
 | 
			
		||||
	//TODO: how to get chat to set
 | 
			
		||||
	currentReceiver = wid
 | 
			
		||||
	textView.Clear()
 | 
			
		||||
	textView.SetTitle(messages.GetIdName(wid))
 | 
			
		||||
	msgTxt, regIds := msgStore.GetMessagesString(wid)
 | 
			
		||||
	textView.SetText(msgTxt)
 | 
			
		||||
	curRegions = regIds
 | 
			
		||||
	textView.SetTitle(wid.Name)
 | 
			
		||||
	sessionManager.CommandChannel <- messages.Command{"select", []string{currentReceiver.Id}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// starts the receiver and message handling thread
 | 
			
		||||
func StartTextReceiver() error {
 | 
			
		||||
	var wac = messages.GetConnection()
 | 
			
		||||
	err := messages.LoginWithConnection(wac)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("%v\n", err)
 | 
			
		||||
// get a string representation of all messages for chat
 | 
			
		||||
func getMessagesString(msgs []messages.Message) string {
 | 
			
		||||
	out := ""
 | 
			
		||||
	for _, msg := range msgs {
 | 
			
		||||
		out += getTextMessageString(&msg)
 | 
			
		||||
		out += "\n"
 | 
			
		||||
	}
 | 
			
		||||
	handler = textHandler{}
 | 
			
		||||
	wac.AddHandler(handler)
 | 
			
		||||
	sendChannel = make(chan waMsg)
 | 
			
		||||
	textChannel = make(chan whatsapp.TextMessage)
 | 
			
		||||
	otherChannel = make(chan interface{})
 | 
			
		||||
	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)
 | 
			
		||||
			}
 | 
			
		||||
		case other := <-otherChannel:
 | 
			
		||||
			msgStore.AddOtherMessage(&other)
 | 
			
		||||
		case contact := <-contactChannel:
 | 
			
		||||
			messages.SetIdName(contact.Jid, contact.Name)
 | 
			
		||||
			app.QueueUpdateDraw(LoadContacts)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(textView, "closing the receiver")
 | 
			
		||||
	wac.Disconnect()
 | 
			
		||||
	return nil
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sends text to whatsapp id
 | 
			
		||||
func SendText(wid string, text string) {
 | 
			
		||||
	msg := whatsapp.TextMessage{
 | 
			
		||||
		Info: whatsapp.MessageInfo{
 | 
			
		||||
			RemoteJid: wid,
 | 
			
		||||
			FromMe:    true,
 | 
			
		||||
			Timestamp: uint64(time.Now().Unix()),
 | 
			
		||||
		},
 | 
			
		||||
		Text: text,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := messages.GetConnection().Send(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		PrintError(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		msgStore.AddTextMessage(&msg)
 | 
			
		||||
		PrintTextMessage(msg)
 | 
			
		||||
// create a formatted string with regions based on message ID from a text message
 | 
			
		||||
func getTextMessageString(msg *messages.Message) string {
 | 
			
		||||
	colorMe := config.Config.Colors.ChatMe
 | 
			
		||||
	colorContact := config.Config.Colors.ChatContact
 | 
			
		||||
	out := ""
 | 
			
		||||
	text := tview.Escape(msg.Text)
 | 
			
		||||
	tim := time.Unix(int64(msg.Timestamp), 0)
 | 
			
		||||
	time := tim.Format("02-01-06 15:04:05")
 | 
			
		||||
	out += "[\""
 | 
			
		||||
	out += msg.Id
 | 
			
		||||
	out += "\"]"
 | 
			
		||||
	if msg.FromMe { //msg from me
 | 
			
		||||
		out += "[-::d](" + time + ") [" + colorMe + "::b]Me: [-::-]" + text
 | 
			
		||||
	} else { // message from others
 | 
			
		||||
		out += "[-::d](" + time + ") [" + colorContact + "::b]" + msg.ContactShort + ": [-::-]" + text
 | 
			
		||||
	}
 | 
			
		||||
	out += "[\"\"]"
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handler struct for whatsapp callbacks
 | 
			
		||||
type textHandler struct{}
 | 
			
		||||
type UiHandler struct{}
 | 
			
		||||
 | 
			
		||||
// HandleError implements the error handler interface for go-whatsapp
 | 
			
		||||
func (t textHandler) HandleError(err error) {
 | 
			
		||||
	PrintText("[red]go-whatsapp reported an error:[-]")
 | 
			
		||||
	PrintError(err)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleTextMessage implements the text message handler interface for go-whatsapp
 | 
			
		||||
func (t textHandler) HandleTextMessage(msg whatsapp.TextMessage) {
 | 
			
		||||
	textChannel <- msg
 | 
			
		||||
	if msg.Info.RemoteJid != currentReceiver {
 | 
			
		||||
		NotifyMsg(msg)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	PrintTextMessage(msg)
 | 
			
		||||
	// add to regions if current window, otherwise its not selectable
 | 
			
		||||
	id := msg.Info.Id
 | 
			
		||||
	app.QueueUpdate(func() {
 | 
			
		||||
		curRegions = append(curRegions, id)
 | 
			
		||||
func (u UiHandler) NewMessage(msg messages.Message) {
 | 
			
		||||
	//TODO: its stupid to "go" this as its supposed to run
 | 
			
		||||
	//on the ui thread anyway. But QueueUpdate blocks...?
 | 
			
		||||
	go app.QueueUpdateDraw(func() {
 | 
			
		||||
		curRegions = append(curRegions, msg)
 | 
			
		||||
		PrintText(getTextMessageString(&msg))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// methods to convert messages to TextMessage
 | 
			
		||||
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,
 | 
			
		||||
			Id:        message.Info.Id,
 | 
			
		||||
		},
 | 
			
		||||
		Text: "[IMAGE] " + message.Caption,
 | 
			
		||||
	}
 | 
			
		||||
	t.HandleTextMessage(msg)
 | 
			
		||||
	otherChannel <- message
 | 
			
		||||
func (u UiHandler) NewScreen(msgs []messages.Message) {
 | 
			
		||||
	go app.QueueUpdateDraw(func() {
 | 
			
		||||
		textView.Clear()
 | 
			
		||||
		screen := getMessagesString(msgs)
 | 
			
		||||
		textView.SetText(screen)
 | 
			
		||||
		curRegions = msgs
 | 
			
		||||
		if screen == "" {
 | 
			
		||||
			PrintHelp()
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
			Id:        message.Info.Id,
 | 
			
		||||
		},
 | 
			
		||||
		Text: "[DOCUMENT] " + message.Title,
 | 
			
		||||
	}
 | 
			
		||||
	t.HandleTextMessage(msg)
 | 
			
		||||
	otherChannel <- message
 | 
			
		||||
// loads the chat data from storage to the TreeView
 | 
			
		||||
func (u UiHandler) SetChats(ids []messages.Chat) {
 | 
			
		||||
	go app.QueueUpdateDraw(func() {
 | 
			
		||||
		chatRoot.ClearChildren()
 | 
			
		||||
		for _, element := range ids {
 | 
			
		||||
			name := element.Name
 | 
			
		||||
			if name == "" {
 | 
			
		||||
				name = strings.TrimSuffix(strings.TrimSuffix(element.Id, messages.GROUPSUFFIX), messages.CONTACTSUFFIX)
 | 
			
		||||
			}
 | 
			
		||||
			if element.Unread > 0 {
 | 
			
		||||
				name += " ([" + config.Config.Colors.UnreadCount + "]" + fmt.Sprint(element.Unread) + "[-])"
 | 
			
		||||
				//tim := time.Unix(element.LastMessage, 0)
 | 
			
		||||
				//sin := time.Since(tim)
 | 
			
		||||
				//since := fmt.Sprintf("%s", sin)
 | 
			
		||||
				//time := tim.Format("02-01-06 15:04:05")
 | 
			
		||||
				//name += since
 | 
			
		||||
			}
 | 
			
		||||
			node := tview.NewTreeNode(name).
 | 
			
		||||
				SetReference(element).
 | 
			
		||||
				SetSelectable(true)
 | 
			
		||||
			if element.IsGroup {
 | 
			
		||||
				node.SetColor(tcell.ColorNames[config.Config.Colors.ListGroup])
 | 
			
		||||
			} else {
 | 
			
		||||
				node.SetColor(tcell.ColorNames[config.Config.Colors.ListContact])
 | 
			
		||||
			}
 | 
			
		||||
			chatRoot.AddChild(node)
 | 
			
		||||
			if element == currentReceiver {
 | 
			
		||||
				treeView.SetCurrentNode(node)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
			Id:        message.Info.Id,
 | 
			
		||||
		},
 | 
			
		||||
		Text: "[VIDEO] " + message.Caption,
 | 
			
		||||
	}
 | 
			
		||||
	t.HandleTextMessage(msg)
 | 
			
		||||
	otherChannel <- message
 | 
			
		||||
func (u UiHandler) PrintError(err error) {
 | 
			
		||||
	PrintError(err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
			Id:        message.Info.Id,
 | 
			
		||||
		},
 | 
			
		||||
		Text: "[AUDIO]",
 | 
			
		||||
	}
 | 
			
		||||
	t.HandleTextMessage(msg)
 | 
			
		||||
	otherChannel <- message
 | 
			
		||||
func (u UiHandler) PrintText(msg string) {
 | 
			
		||||
	PrintText(msg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add contact info to database (not needed, internal db of connection is used)
 | 
			
		||||
func (t textHandler) HandleNewContact(contact whatsapp.Contact) {
 | 
			
		||||
	// redundant, wac has contacts
 | 
			
		||||
	//contactChannel <- contact
 | 
			
		||||
func (u UiHandler) PrintFile(path string) {
 | 
			
		||||
	PrintImage(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handle battery messages
 | 
			
		||||
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
 | 
			
		||||
//  app.QueueUpdate(func() {
 | 
			
		||||
//    infoBar.SetText("🔋: " + string(msg.Percentage) + "%")
 | 
			
		||||
//  })
 | 
			
		||||
//}
 | 
			
		||||
func (u UiHandler) OpenFile(path string) {
 | 
			
		||||
	open.Run(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u UiHandler) SetStatus(status messages.SessionStatus) {
 | 
			
		||||
	UpdateStatusBar(status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u UiHandler) GetWriter() io.Writer {
 | 
			
		||||
	return textView
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,99 +0,0 @@
 | 
			
		||||
package messages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/gob"
 | 
			
		||||
	"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(config.GetContactsFilePath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 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)
 | 
			
		||||
	err = decoder.Decode(&contacts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// saves custom contacts to disk
 | 
			
		||||
func SaveContacts() {
 | 
			
		||||
	file, err := os.Open(config.GetContactsFilePath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
	encoder := gob.NewEncoder(file)
 | 
			
		||||
	err = encoder.Encode(contacts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
	}
 | 
			
		||||
	return usr.HomeDir + string(os.PathSeparator)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								messages/messages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								messages/messages.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
//this package manages the messages
 | 
			
		||||
package messages
 | 
			
		||||
 | 
			
		||||
import "io"
 | 
			
		||||
 | 
			
		||||
// TODO: move these funcs/interface to channels
 | 
			
		||||
type UiMessageHandler interface {
 | 
			
		||||
	NewMessage(Message)
 | 
			
		||||
	NewScreen([]Message)
 | 
			
		||||
	SetChats([]Chat)
 | 
			
		||||
	PrintError(error)
 | 
			
		||||
	PrintText(string)
 | 
			
		||||
	PrintFile(string)
 | 
			
		||||
	SetStatus(SessionStatus)
 | 
			
		||||
	OpenFile(string)
 | 
			
		||||
	GetWriter() io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// data struct for current session status
 | 
			
		||||
type SessionStatus struct {
 | 
			
		||||
	BatteryCharge    int
 | 
			
		||||
	BatteryLoading   bool
 | 
			
		||||
	BatteryPowersave bool
 | 
			
		||||
	Connected        bool
 | 
			
		||||
	LastSeen         string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// message struct for battery messages
 | 
			
		||||
type BatteryMsg struct {
 | 
			
		||||
	charge    int
 | 
			
		||||
	loading   bool
 | 
			
		||||
	powersave bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// message struct for status messages
 | 
			
		||||
type StatusMsg struct {
 | 
			
		||||
	connected bool
 | 
			
		||||
	err       error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// message object for commands
 | 
			
		||||
type Command struct {
 | 
			
		||||
	Name   string
 | 
			
		||||
	Params []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// internal message representation to abstract from message lib
 | 
			
		||||
type Message struct {
 | 
			
		||||
	Id           string
 | 
			
		||||
	ChatId       string // the source of the message (group id or contact id)
 | 
			
		||||
	ContactId    string
 | 
			
		||||
	ContactName  string
 | 
			
		||||
	ContactShort string
 | 
			
		||||
	Timestamp    uint64
 | 
			
		||||
	FromMe       bool
 | 
			
		||||
	Text         string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// internal contact representation to abstract from message lib
 | 
			
		||||
type Chat struct {
 | 
			
		||||
	Id      string
 | 
			
		||||
	IsGroup bool
 | 
			
		||||
	Name    string
 | 
			
		||||
	Unread  int
 | 
			
		||||
	//TODO: convert to uint64
 | 
			
		||||
	LastMessage int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Contact struct {
 | 
			
		||||
	Id    string
 | 
			
		||||
	Name  string
 | 
			
		||||
	Short string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const GROUPSUFFIX = "@g.us"
 | 
			
		||||
const CONTACTSUFFIX = "@s.whatsapp.net"
 | 
			
		||||
const STATUSSUFFIX = "status@broadcast"
 | 
			
		||||
@@ -2,58 +2,211 @@ package messages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/gob"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gabriel-vasile/mimetype"
 | 
			
		||||
	"github.com/gdamore/tcell/v2"
 | 
			
		||||
	"github.com/gen2brain/beeep"
 | 
			
		||||
	"github.com/rivo/tview"
 | 
			
		||||
	"mvdan.cc/xurls/v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
	"github.com/normen/whatscli/config"
 | 
			
		||||
	"github.com/normen/whatscli/qrcode"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var textView *tview.TextView
 | 
			
		||||
var connMutex sync.Mutex
 | 
			
		||||
// SessionManager deals with the connection and receives commands from the UI
 | 
			
		||||
// it updates the UI accordingly
 | 
			
		||||
type SessionManager struct {
 | 
			
		||||
	db              *MessageDatabase
 | 
			
		||||
	currentReceiver string // currently selected chat for message handling
 | 
			
		||||
	uiHandler       UiMessageHandler
 | 
			
		||||
	connection      *whatsapp.Conn
 | 
			
		||||
	BatteryChannel  chan BatteryMsg
 | 
			
		||||
	StatusChannel   chan StatusMsg
 | 
			
		||||
	CommandChannel  chan Command
 | 
			
		||||
	ChatChannel     chan whatsapp.Chat
 | 
			
		||||
	ContactChannel  chan whatsapp.Contact
 | 
			
		||||
	TextChannel     chan whatsapp.TextMessage
 | 
			
		||||
	OtherChannel    chan interface{}
 | 
			
		||||
	statusInfo      SessionStatus
 | 
			
		||||
	lastSent        time.Time
 | 
			
		||||
	started         bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: remove this circular dependeny in favor of a better way
 | 
			
		||||
func SetTextView(tv *tview.TextView) {
 | 
			
		||||
	textView = tv
 | 
			
		||||
// initialize the SessionManager
 | 
			
		||||
func (sm *SessionManager) Init(handler UiMessageHandler) {
 | 
			
		||||
	sm.db = &MessageDatabase{}
 | 
			
		||||
	sm.db.Init()
 | 
			
		||||
	sm.uiHandler = handler
 | 
			
		||||
	sm.BatteryChannel = make(chan BatteryMsg, 10)
 | 
			
		||||
	sm.StatusChannel = make(chan StatusMsg, 10)
 | 
			
		||||
	sm.CommandChannel = make(chan Command, 10)
 | 
			
		||||
	sm.ChatChannel = make(chan whatsapp.Chat, 10)
 | 
			
		||||
	sm.ContactChannel = make(chan whatsapp.Contact, 10)
 | 
			
		||||
	sm.TextChannel = make(chan whatsapp.TextMessage, 10)
 | 
			
		||||
	sm.OtherChannel = make(chan interface{}, 10)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// starts the receiver and message handling go routine
 | 
			
		||||
func (sm *SessionManager) StartManager() error {
 | 
			
		||||
	if sm.started {
 | 
			
		||||
		return errors.New("session manager running, send commands to control")
 | 
			
		||||
	}
 | 
			
		||||
	sm.started = true
 | 
			
		||||
	go sm.runManager()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sm *SessionManager) runManager() error {
 | 
			
		||||
	var wac = sm.getConnection()
 | 
			
		||||
	err := sm.loginWithConnection(wac)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	}
 | 
			
		||||
	wac.AddHandler(sm)
 | 
			
		||||
	for sm.started == true {
 | 
			
		||||
		select {
 | 
			
		||||
		case msg := <-sm.TextChannel:
 | 
			
		||||
			didNew := sm.db.AddTextMessage(&msg)
 | 
			
		||||
			if msg.Info.RemoteJid == sm.currentReceiver {
 | 
			
		||||
				if didNew {
 | 
			
		||||
					sm.uiHandler.NewMessage(sm.createMessage(&msg))
 | 
			
		||||
				} else {
 | 
			
		||||
					screen := sm.getMessages(sm.currentReceiver)
 | 
			
		||||
					sm.uiHandler.NewScreen(screen)
 | 
			
		||||
				}
 | 
			
		||||
				// notify if chat is in focus and we didn't send a message recently
 | 
			
		||||
				// TODO: move to UI (when UI has time in messages)
 | 
			
		||||
				if config.Config.General.EnableNotifications && !msg.Info.FromMe {
 | 
			
		||||
					if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
 | 
			
		||||
						if int64(msg.Info.Timestamp) > sm.lastSent.Unix()+config.Config.General.NotificationTimeout {
 | 
			
		||||
							err := beeep.Notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
 | 
			
		||||
							if err != nil {
 | 
			
		||||
								sm.uiHandler.PrintError(err)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if config.Config.General.EnableNotifications && !msg.Info.FromMe {
 | 
			
		||||
					// notify if message is younger than 30 sec and not in focus
 | 
			
		||||
					if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
 | 
			
		||||
						err := beeep.Notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							sm.uiHandler.PrintError(err)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			sm.uiHandler.SetChats(sm.db.GetChatIds())
 | 
			
		||||
		case other := <-sm.OtherChannel:
 | 
			
		||||
			sm.db.AddOtherMessage(&other)
 | 
			
		||||
		case c := <-sm.ContactChannel:
 | 
			
		||||
			contact := Contact{
 | 
			
		||||
				c.Jid,
 | 
			
		||||
				c.Name,
 | 
			
		||||
				c.Short,
 | 
			
		||||
			}
 | 
			
		||||
			if contact.Name == "" && c.Notify != "" {
 | 
			
		||||
				contact.Name = c.Notify
 | 
			
		||||
			}
 | 
			
		||||
			if contact.Short == "" && c.Notify != "" {
 | 
			
		||||
				contact.Short = c.Notify
 | 
			
		||||
			}
 | 
			
		||||
			sm.db.AddContact(contact)
 | 
			
		||||
			sm.uiHandler.SetChats(sm.db.GetChatIds())
 | 
			
		||||
		case c := <-sm.ChatChannel:
 | 
			
		||||
			if c.IsMarkedSpam == "false" {
 | 
			
		||||
				isGroup := strings.Contains(c.Jid, GROUPSUFFIX)
 | 
			
		||||
				unread, _ := strconv.ParseInt(c.Unread, 10, 0)
 | 
			
		||||
				last, _ := strconv.ParseInt(c.LastMessageTime, 10, 64)
 | 
			
		||||
				chat := Chat{
 | 
			
		||||
					c.Jid,
 | 
			
		||||
					isGroup,
 | 
			
		||||
					c.Name,
 | 
			
		||||
					int(unread),
 | 
			
		||||
					last,
 | 
			
		||||
				}
 | 
			
		||||
				sm.db.AddChat(chat)
 | 
			
		||||
				sm.uiHandler.SetChats(sm.db.GetChatIds())
 | 
			
		||||
			}
 | 
			
		||||
		case command := <-sm.CommandChannel:
 | 
			
		||||
			sm.execCommand(command)
 | 
			
		||||
		case batteryMsg := <-sm.BatteryChannel:
 | 
			
		||||
			sm.statusInfo.BatteryLoading = batteryMsg.loading
 | 
			
		||||
			sm.statusInfo.BatteryPowersave = batteryMsg.powersave
 | 
			
		||||
			sm.statusInfo.BatteryCharge = batteryMsg.charge
 | 
			
		||||
			sm.uiHandler.SetStatus(sm.statusInfo)
 | 
			
		||||
		case statusMsg := <-sm.StatusChannel:
 | 
			
		||||
			prevStatus := sm.statusInfo.Connected
 | 
			
		||||
			if statusMsg.err != nil {
 | 
			
		||||
			} else {
 | 
			
		||||
				sm.statusInfo.Connected = statusMsg.connected
 | 
			
		||||
			}
 | 
			
		||||
			wac := sm.getConnection()
 | 
			
		||||
			connected := wac.GetConnected()
 | 
			
		||||
			sm.statusInfo.Connected = connected
 | 
			
		||||
			sm.uiHandler.SetStatus(sm.statusInfo)
 | 
			
		||||
			if prevStatus != sm.statusInfo.Connected {
 | 
			
		||||
				if sm.statusInfo.Connected {
 | 
			
		||||
					sm.uiHandler.PrintText("connected")
 | 
			
		||||
				} else {
 | 
			
		||||
					sm.uiHandler.PrintText("disconnected")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(sm.uiHandler.GetWriter(), "closing the receiver")
 | 
			
		||||
	wac.Disconnect()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// set the currently selected chat
 | 
			
		||||
func (sm *SessionManager) setCurrentReceiver(id string) {
 | 
			
		||||
	sm.currentReceiver = id
 | 
			
		||||
	screen := sm.getMessages(id)
 | 
			
		||||
	sm.uiHandler.NewScreen(screen)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// gets an existing connection or creates one
 | 
			
		||||
func GetConnection() *whatsapp.Conn {
 | 
			
		||||
	connMutex.Lock()
 | 
			
		||||
	defer connMutex.Unlock()
 | 
			
		||||
func (sm *SessionManager) getConnection() *whatsapp.Conn {
 | 
			
		||||
	var wac *whatsapp.Conn
 | 
			
		||||
	if connection == nil {
 | 
			
		||||
	if sm.connection == nil {
 | 
			
		||||
		wacc, err := whatsapp.NewConn(5 * time.Second)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		wac = wacc
 | 
			
		||||
		connection = wac
 | 
			
		||||
		sm.connection = wac
 | 
			
		||||
		//wac.SetClientVersion(2, 2021, 4)
 | 
			
		||||
	} else {
 | 
			
		||||
		wac = connection
 | 
			
		||||
		wac = sm.connection
 | 
			
		||||
	}
 | 
			
		||||
	return wac
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Login logs in the user. It ries to see if a session already exists. If not, tries to create a
 | 
			
		||||
// 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())
 | 
			
		||||
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
 | 
			
		||||
// 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 {
 | 
			
		||||
	connMutex.Lock()
 | 
			
		||||
	defer connMutex.Unlock()
 | 
			
		||||
func (sm *SessionManager) loginWithConnection(wac *whatsapp.Conn) error {
 | 
			
		||||
	sm.uiHandler.PrintText("connecting..")
 | 
			
		||||
	if wac != nil && wac.GetConnected() {
 | 
			
		||||
		wac.Disconnect()
 | 
			
		||||
		sm.StatusChannel <- StatusMsg{false, nil}
 | 
			
		||||
	}
 | 
			
		||||
	//load saved session
 | 
			
		||||
	session, err := readSession()
 | 
			
		||||
@@ -68,7 +221,7 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
 | 
			
		||||
		qr := make(chan string)
 | 
			
		||||
		go func() {
 | 
			
		||||
			terminal := qrcode.New()
 | 
			
		||||
			terminal.SetOutput(tview.ANSIWriter(textView))
 | 
			
		||||
			terminal.SetOutput(tview.ANSIWriter(sm.uiHandler.GetWriter()))
 | 
			
		||||
			terminal.Get(<-qr).Print()
 | 
			
		||||
		}()
 | 
			
		||||
		session, err = wac.Login(qr)
 | 
			
		||||
@@ -83,36 +236,575 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
 | 
			
		||||
		return fmt.Errorf("error saving session: %v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	//<-time.After(3 * time.Second)
 | 
			
		||||
	sm.StatusChannel <- StatusMsg{true, nil}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Disconnect() error {
 | 
			
		||||
	wac := GetConnection()
 | 
			
		||||
// disconnects the session
 | 
			
		||||
func (sm *SessionManager) disconnect() error {
 | 
			
		||||
	wac := sm.getConnection()
 | 
			
		||||
	var err error
 | 
			
		||||
	if wac != nil && wac.GetConnected() {
 | 
			
		||||
		_, err := wac.Disconnect()
 | 
			
		||||
		return err
 | 
			
		||||
		_, err = wac.Disconnect()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	sm.StatusChannel <- StatusMsg{false, err}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Logout logs out the user.
 | 
			
		||||
func Logout() error {
 | 
			
		||||
	connMutex.Lock()
 | 
			
		||||
	defer connMutex.Unlock()
 | 
			
		||||
// logout logs out the user, deletes session file
 | 
			
		||||
func (ub *SessionManager) logout() error {
 | 
			
		||||
	ub.getConnection().Disconnect()
 | 
			
		||||
	return removeSession()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// executes a command
 | 
			
		||||
func (sm *SessionManager) execCommand(command Command) {
 | 
			
		||||
	cmd := command.Name
 | 
			
		||||
	switch cmd {
 | 
			
		||||
	default:
 | 
			
		||||
		sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Unknown command: [-]" + cmd)
 | 
			
		||||
	case "backlog":
 | 
			
		||||
		if sm.currentReceiver != "" {
 | 
			
		||||
			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)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				go sm.getConnection().LoadChatMessages(sm.currentReceiver, count, "", false, false, sm)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("backlog", "-> only works in a chat")
 | 
			
		||||
		}
 | 
			
		||||
	case "login":
 | 
			
		||||
		sm.uiHandler.PrintError(sm.login())
 | 
			
		||||
	case "connect":
 | 
			
		||||
		sm.uiHandler.PrintError(sm.login())
 | 
			
		||||
	case "disconnect":
 | 
			
		||||
		sm.uiHandler.PrintError(sm.disconnect())
 | 
			
		||||
	case "logout":
 | 
			
		||||
		sm.uiHandler.PrintError(sm.logout())
 | 
			
		||||
	case "send":
 | 
			
		||||
		if checkParam(command.Params, 2) {
 | 
			
		||||
			textParams := command.Params[1:]
 | 
			
		||||
			text := strings.Join(textParams, " ")
 | 
			
		||||
			sm.sendText(command.Params[0], text)
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("send", "[chat-id[] [message text[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "select":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			sm.setCurrentReceiver(command.Params[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("select", "[chat-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "read":
 | 
			
		||||
		if sm.currentReceiver != "" {
 | 
			
		||||
			// need to send message id, so get all (unread count)
 | 
			
		||||
			// recent messages and send "read"
 | 
			
		||||
			if chat, ok := sm.db.chats[sm.currentReceiver]; ok {
 | 
			
		||||
				count := chat.Unread
 | 
			
		||||
				msgs := sm.db.GetMessages(chat.Id)
 | 
			
		||||
				length := len(msgs)
 | 
			
		||||
				for idx, msg := range msgs {
 | 
			
		||||
					if idx >= length-count {
 | 
			
		||||
						sm.getConnection().Read(chat.Id, msg.Info.Id)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("read", "-> only works in a chat")
 | 
			
		||||
		}
 | 
			
		||||
	case "info":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			sm.uiHandler.PrintText(sm.db.GetMessageInfo(command.Params[0]))
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("info", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "download":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			if path, err := sm.downloadMessage(command.Params[0], false); err != nil {
 | 
			
		||||
				sm.uiHandler.PrintError(err)
 | 
			
		||||
			} else {
 | 
			
		||||
				sm.uiHandler.PrintText("[::d] -> " + path + "[::-]")
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("download", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "open":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			if path, err := sm.downloadMessage(command.Params[0], true); err == nil {
 | 
			
		||||
				sm.uiHandler.OpenFile(path)
 | 
			
		||||
			} else {
 | 
			
		||||
				sm.uiHandler.PrintError(err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("open", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "show":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			if path, err := sm.downloadMessage(command.Params[0], true); err == nil {
 | 
			
		||||
				sm.uiHandler.PrintFile(path)
 | 
			
		||||
			} else {
 | 
			
		||||
				sm.uiHandler.PrintError(err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("show", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "url":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			if msg, ok := sm.db.messagesById[command.Params[0]]; ok {
 | 
			
		||||
				urlParser := xurls.Relaxed()
 | 
			
		||||
				url := urlParser.FindString(msg.Text)
 | 
			
		||||
				if url != "" {
 | 
			
		||||
					sm.uiHandler.OpenFile(url)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("url", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "upload":
 | 
			
		||||
		if sm.currentReceiver == "" {
 | 
			
		||||
			sm.printCommandUsage("upload", "-> only works in a chat")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		var mime *mimetype.MIME
 | 
			
		||||
		var file *os.File
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			path := strings.Join(command.Params, " ")
 | 
			
		||||
			if mime, err = mimetype.DetectFile(path); err == nil {
 | 
			
		||||
				if file, err = os.Open(path); err == nil {
 | 
			
		||||
					msg := whatsapp.DocumentMessage{
 | 
			
		||||
						Info: whatsapp.MessageInfo{
 | 
			
		||||
							RemoteJid: sm.currentReceiver,
 | 
			
		||||
						},
 | 
			
		||||
						Type:     mime.String(),
 | 
			
		||||
						FileName: filepath.Base(file.Name()),
 | 
			
		||||
					}
 | 
			
		||||
					wac := sm.getConnection()
 | 
			
		||||
					sm.lastSent = time.Now()
 | 
			
		||||
					_, err = wac.Send(msg)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("upload", "/path/to/file")
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	case "sendimage":
 | 
			
		||||
		if sm.currentReceiver == "" {
 | 
			
		||||
			sm.printCommandUsage("sendimage", "-> only works in a chat")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		var mime *mimetype.MIME
 | 
			
		||||
		var file *os.File
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			path := strings.Join(command.Params, " ")
 | 
			
		||||
			if mime, err = mimetype.DetectFile(path); err == nil {
 | 
			
		||||
				if file, err = os.Open(path); err == nil {
 | 
			
		||||
					msg := whatsapp.ImageMessage{
 | 
			
		||||
						Info: whatsapp.MessageInfo{
 | 
			
		||||
							RemoteJid: sm.currentReceiver,
 | 
			
		||||
						},
 | 
			
		||||
						Type:    mime.String(),
 | 
			
		||||
						Content: file,
 | 
			
		||||
					}
 | 
			
		||||
					wac := sm.getConnection()
 | 
			
		||||
					sm.lastSent = time.Now()
 | 
			
		||||
					_, err = wac.Send(msg)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("sendimage", "/path/to/file")
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	case "sendvideo":
 | 
			
		||||
		if sm.currentReceiver == "" {
 | 
			
		||||
			sm.printCommandUsage("sendvideo", "-> only works in a chat")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		var mime *mimetype.MIME
 | 
			
		||||
		var file *os.File
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			path := strings.Join(command.Params, " ")
 | 
			
		||||
			if mime, err = mimetype.DetectFile(path); err == nil {
 | 
			
		||||
				if file, err = os.Open(path); err == nil {
 | 
			
		||||
					msg := whatsapp.VideoMessage{
 | 
			
		||||
						Info: whatsapp.MessageInfo{
 | 
			
		||||
							RemoteJid: sm.currentReceiver,
 | 
			
		||||
						},
 | 
			
		||||
						Type:    mime.String(),
 | 
			
		||||
						Content: file,
 | 
			
		||||
					}
 | 
			
		||||
					wac := sm.getConnection()
 | 
			
		||||
					sm.lastSent = time.Now()
 | 
			
		||||
					_, err = wac.Send(msg)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("sendvideo", "/path/to/file")
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	case "sendaudio":
 | 
			
		||||
		if sm.currentReceiver == "" {
 | 
			
		||||
			sm.printCommandUsage("sendaudio", "-> only works in a chat")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		var mime *mimetype.MIME
 | 
			
		||||
		var file *os.File
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			path := strings.Join(command.Params, " ")
 | 
			
		||||
			if mime, err = mimetype.DetectFile(path); err == nil {
 | 
			
		||||
				if file, err = os.Open(path); err == nil {
 | 
			
		||||
					msg := whatsapp.AudioMessage{
 | 
			
		||||
						Info: whatsapp.MessageInfo{
 | 
			
		||||
							RemoteJid: sm.currentReceiver,
 | 
			
		||||
						},
 | 
			
		||||
						Type:    mime.String(),
 | 
			
		||||
						Content: file,
 | 
			
		||||
					}
 | 
			
		||||
					wac := sm.getConnection()
 | 
			
		||||
					sm.lastSent = time.Now()
 | 
			
		||||
					_, err = wac.Send(msg)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("sendaudio", "/path/to/file")
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	case "revoke":
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			wac := sm.getConnection()
 | 
			
		||||
			var revId string
 | 
			
		||||
			var err error
 | 
			
		||||
			if msgg, ok := sm.db.otherMessages[command.Params[0]]; ok {
 | 
			
		||||
				switch msg := (*msgg).(type) {
 | 
			
		||||
				default:
 | 
			
		||||
				case whatsapp.ImageMessage:
 | 
			
		||||
					revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
 | 
			
		||||
				case whatsapp.DocumentMessage:
 | 
			
		||||
					revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
 | 
			
		||||
				case whatsapp.AudioMessage:
 | 
			
		||||
					revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
 | 
			
		||||
				case whatsapp.VideoMessage:
 | 
			
		||||
					revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if msg, ok := sm.db.messagesById[command.Params[0]]; ok {
 | 
			
		||||
					revId, err = wac.RevokeMessage(msg.Info.RemoteJid, msg.Info.Id, msg.Info.FromMe)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				sm.uiHandler.PrintText("revoked: " + revId)
 | 
			
		||||
			}
 | 
			
		||||
			sm.uiHandler.PrintError(err)
 | 
			
		||||
		} else {
 | 
			
		||||
			sm.printCommandUsage("revoke", "[message-id[]")
 | 
			
		||||
		}
 | 
			
		||||
	case "leave":
 | 
			
		||||
		groupId := sm.currentReceiver
 | 
			
		||||
		if checkParam(command.Params, 1) {
 | 
			
		||||
			groupId = command.Params[0]
 | 
			
		||||
		}
 | 
			
		||||
		wac := sm.getConnection()
 | 
			
		||||
		var err error
 | 
			
		||||
		_, err = wac.LeaveGroup(groupId)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			sm.uiHandler.PrintText("left group " + groupId)
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	case "colorlist":
 | 
			
		||||
		out := ""
 | 
			
		||||
		for idx, _ := range tcell.ColorNames {
 | 
			
		||||
			out = out + "[" + idx + "]" + idx + "[-]\n"
 | 
			
		||||
		}
 | 
			
		||||
		sm.uiHandler.PrintText(out)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// helper for built-in command help
 | 
			
		||||
func (sm *SessionManager) printCommandUsage(command string, usage string) {
 | 
			
		||||
	sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]Usage:[-] " + command + " " + usage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// check if parameters for command are okay
 | 
			
		||||
func checkParam(arr []string, length int) bool {
 | 
			
		||||
	if arr == nil || len(arr) < length {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// get all messages for one chat id
 | 
			
		||||
func (sm *SessionManager) getMessages(wid string) []Message {
 | 
			
		||||
	msgs := sm.db.GetMessages(wid)
 | 
			
		||||
	ids := []Message{}
 | 
			
		||||
	for _, msg := range msgs {
 | 
			
		||||
		ids = append(ids, sm.createMessage(&msg))
 | 
			
		||||
	}
 | 
			
		||||
	return ids
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// create internal message from whatsapp message
 | 
			
		||||
// TODO: store these instead of generating each time
 | 
			
		||||
func (sm *SessionManager) createMessage(msg *whatsapp.TextMessage) Message {
 | 
			
		||||
	newMsg := Message{}
 | 
			
		||||
	newMsg.Id = msg.Info.Id
 | 
			
		||||
	newMsg.ChatId = msg.Info.RemoteJid
 | 
			
		||||
	newMsg.FromMe = msg.Info.FromMe
 | 
			
		||||
	newMsg.Timestamp = msg.Info.Timestamp
 | 
			
		||||
	newMsg.Text = msg.Text
 | 
			
		||||
	if strings.Contains(msg.Info.RemoteJid, STATUSSUFFIX) {
 | 
			
		||||
		newMsg.ContactId = msg.Info.SenderJid
 | 
			
		||||
		newMsg.ContactName = sm.db.GetIdName(msg.Info.SenderJid)
 | 
			
		||||
		newMsg.ContactShort = sm.db.GetIdShort(msg.Info.SenderJid)
 | 
			
		||||
	} else if strings.Contains(msg.Info.RemoteJid, GROUPSUFFIX) {
 | 
			
		||||
		newMsg.ContactId = msg.Info.SenderJid
 | 
			
		||||
		newMsg.ContactName = sm.db.GetIdName(msg.Info.SenderJid)
 | 
			
		||||
		newMsg.ContactShort = sm.db.GetIdShort(msg.Info.SenderJid)
 | 
			
		||||
	} else {
 | 
			
		||||
		newMsg.ContactId = msg.Info.RemoteJid
 | 
			
		||||
		newMsg.ContactName = sm.db.GetIdName(msg.Info.RemoteJid)
 | 
			
		||||
		newMsg.ContactShort = sm.db.GetIdShort(msg.Info.RemoteJid)
 | 
			
		||||
	}
 | 
			
		||||
	return newMsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.Config.General.PreviewPath
 | 
			
		||||
		} else {
 | 
			
		||||
			fileName += config.Config.General.DownloadPath
 | 
			
		||||
		}
 | 
			
		||||
		fileName += string(os.PathSeparator)
 | 
			
		||||
		switch v := (*msg).(type) {
 | 
			
		||||
		default:
 | 
			
		||||
		case whatsapp.ImageMessage:
 | 
			
		||||
			fileName += v.Info.Id
 | 
			
		||||
			if exts, err := mime.ExtensionsByType(v.Type); err == nil {
 | 
			
		||||
				fileName += exts[0]
 | 
			
		||||
			}
 | 
			
		||||
			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
 | 
			
		||||
			if exts, err := mime.ExtensionsByType(v.Type); err == nil {
 | 
			
		||||
				fileName += exts[0]
 | 
			
		||||
			}
 | 
			
		||||
			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
 | 
			
		||||
			if exts, err := mime.ExtensionsByType(v.Type); err == nil {
 | 
			
		||||
				fileName += exts[0]
 | 
			
		||||
			}
 | 
			
		||||
			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
 | 
			
		||||
			if exts, err := mime.ExtensionsByType(v.Type); err == nil {
 | 
			
		||||
				fileName += exts[0]
 | 
			
		||||
			}
 | 
			
		||||
			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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sm.lastSent = time.Now()
 | 
			
		||||
	_, err := sm.getConnection().Send(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sm.uiHandler.PrintError(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		sm.db.AddTextMessage(&msg)
 | 
			
		||||
		if sm.currentReceiver == wid {
 | 
			
		||||
			sm.uiHandler.NewMessage(sm.createMessage(&msg))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handler struct for whatsapp callbacks
 | 
			
		||||
 | 
			
		||||
// HandleError implements the error handler interface for go-whatsapp
 | 
			
		||||
func (sm *SessionManager) HandleError(err error) {
 | 
			
		||||
	sm.uiHandler.PrintText("[" + config.Config.Colors.Negative + "]go-whatsapp reported an error:[-]")
 | 
			
		||||
	sm.uiHandler.PrintError(err)
 | 
			
		||||
	statusMsg := StatusMsg{false, err}
 | 
			
		||||
	sm.StatusChannel <- statusMsg
 | 
			
		||||
	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
 | 
			
		||||
	sm.ContactChannel <- contact
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handle battery messages
 | 
			
		||||
func (sm *SessionManager) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
 | 
			
		||||
	sm.BatteryChannel <- BatteryMsg{msg.Percentage, msg.Plugged, msg.Powersave}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sm *SessionManager) HandleContactList(contacts []whatsapp.Contact) {
 | 
			
		||||
	for _, c := range contacts {
 | 
			
		||||
		sm.ContactChannel <- c
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sm *SessionManager) HandleChatList(chats []whatsapp.Chat) {
 | 
			
		||||
	for _, c := range chats {
 | 
			
		||||
		sm.ChatChannel <- c
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// helper to save an attachment and open it if specified
 | 
			
		||||
func saveAttachment(data []byte, path string) (string, error) {
 | 
			
		||||
	err := ioutil.WriteFile(path, data, 0644)
 | 
			
		||||
	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")
 | 
			
		||||
		file, err = os.Open(config.GetHomeDir() + ".whatscli.session")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return session, err
 | 
			
		||||
		} else {
 | 
			
		||||
			os.Remove(GetHomeDir() + ".whatscli.session")
 | 
			
		||||
			os.Remove(config.GetHomeDir() + ".whatscli.session")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,19 @@
 | 
			
		||||
package messages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Rhymen/go-whatsapp"
 | 
			
		||||
	"github.com/normen/whatscli/config"
 | 
			
		||||
	"github.com/rivo/tview"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const GROUPSUFFIX = "@g.us"
 | 
			
		||||
const CONTACTSUFFIX = "@s.whatsapp.net"
 | 
			
		||||
 | 
			
		||||
type MessageDatabase struct {
 | 
			
		||||
	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
 | 
			
		||||
	mutex         sync.Mutex
 | 
			
		||||
	contacts      map[string]Contact
 | 
			
		||||
	chats         map[string]Chat
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initialize the database
 | 
			
		||||
@@ -32,12 +23,12 @@ func (db *MessageDatabase) Init() {
 | 
			
		||||
	db.messagesById = make(map[string]*whatsapp.TextMessage)
 | 
			
		||||
	db.otherMessages = make(map[string]*interface{})
 | 
			
		||||
	db.latestMessage = make(map[string]uint64)
 | 
			
		||||
	db.contacts = make(map[string]Contact)
 | 
			
		||||
	db.chats = make(map[string]Chat)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add a text message to the database, stored by RemoteJid
 | 
			
		||||
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
 | 
			
		||||
@@ -50,18 +41,19 @@ func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool {
 | 
			
		||||
		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
 | 
			
		||||
	})
 | 
			
		||||
	//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{}) {
 | 
			
		||||
	db.mutex.Lock()
 | 
			
		||||
	defer db.mutex.Unlock()
 | 
			
		||||
	var id = ""
 | 
			
		||||
	switch v := (*msg).(type) {
 | 
			
		||||
	default:
 | 
			
		||||
@@ -79,26 +71,54 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *MessageDatabase) AddContact(contact Contact) {
 | 
			
		||||
	db.contacts[contact.Id] = contact
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *MessageDatabase) AddChat(chat Chat) {
 | 
			
		||||
	db.chats[chat.Id] = chat
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// get an array of all chat ids
 | 
			
		||||
func (db *MessageDatabase) GetContactIds() []string {
 | 
			
		||||
	db.mutex.Lock()
 | 
			
		||||
	defer db.mutex.Unlock()
 | 
			
		||||
	//var this = *db
 | 
			
		||||
	keys := make([]string, len(db.textMessages))
 | 
			
		||||
func (db *MessageDatabase) GetChatIds() []Chat {
 | 
			
		||||
	keys := make([]Chat, len(db.chats))
 | 
			
		||||
	i := 0
 | 
			
		||||
	for k := range db.textMessages {
 | 
			
		||||
	for _, k := range db.chats {
 | 
			
		||||
		keys[i] = k
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(keys, func(i, j int) bool {
 | 
			
		||||
		return db.latestMessage[keys[i]] > db.latestMessage[keys[j]]
 | 
			
		||||
		return db.latestMessage[keys[i].Id] > db.latestMessage[keys[j].Id]
 | 
			
		||||
	})
 | 
			
		||||
	return keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// gets a pretty name for a whatsapp id
 | 
			
		||||
func (sm *MessageDatabase) GetIdName(id string) string {
 | 
			
		||||
	if val, ok := sm.contacts[id]; ok {
 | 
			
		||||
		if val.Name != "" {
 | 
			
		||||
			return val.Name
 | 
			
		||||
		} else if val.Short != "" {
 | 
			
		||||
			return val.Short
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// gets a short name for a whatsapp id
 | 
			
		||||
func (sm *MessageDatabase) GetIdShort(id string) string {
 | 
			
		||||
	if val, ok := sm.contacts[id]; ok {
 | 
			
		||||
		//TODO val.notify from whatsapp??
 | 
			
		||||
		if val.Short != "" {
 | 
			
		||||
			return val.Short
 | 
			
		||||
		} else if val.Name != "" {
 | 
			
		||||
			return val.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *MessageDatabase) GetMessageInfo(id string) string {
 | 
			
		||||
	db.mutex.Lock()
 | 
			
		||||
	defer db.mutex.Unlock()
 | 
			
		||||
	if _, ok := db.otherMessages[id]; ok {
 | 
			
		||||
		return "[yellow]OtherMessage[-]"
 | 
			
		||||
	}
 | 
			
		||||
@@ -115,128 +135,10 @@ 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{}
 | 
			
		||||
func (db *MessageDatabase) GetMessages(wid string) []whatsapp.TextMessage {
 | 
			
		||||
	var arr = []whatsapp.TextMessage{}
 | 
			
		||||
	for _, element := range db.textMessages[wid] {
 | 
			
		||||
		out += GetTextMessageString(element)
 | 
			
		||||
		out += "\n"
 | 
			
		||||
		arr = append(arr, element.Info.Id)
 | 
			
		||||
		arr = append(arr, *element)
 | 
			
		||||
	}
 | 
			
		||||
	return out, arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// load data for message specified by message id TODO: support types
 | 
			
		||||
func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
 | 
			
		||||
	db.mutex.Lock()
 | 
			
		||||
	defer db.mutex.Unlock()
 | 
			
		||||
	if msg, ok := 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 (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:
 | 
			
		||||
			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
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	return arr
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user