Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49718acc49 | ||
|
|
6a97d6dfe6 | ||
|
|
0bacea701a | ||
|
|
421139730c | ||
|
|
313b4606c5 | ||
|
|
f79e3e46b7 | ||
|
|
3053d15929 | ||
|
|
73cd749394 | ||
|
|
8e8f2da43f | ||
|
|
e8d9f266fa | ||
|
|
2763bb14b4 | ||
|
|
f91ffbef0f | ||
|
|
3521a3b6f5 | ||
|
|
a6d7954795 | ||
|
|
96e9d75810 | ||
|
|
b2904929b0 | ||
|
|
033e7aa1ac | ||
|
|
53e404dd55 | ||
|
|
8318d2e80f | ||
|
|
3af2a3738f | ||
|
|
e31c2c3e36 | ||
|
|
3f20981550 | ||
|
|
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 | ||
|
|
42daf1a9f7 | ||
|
|
04960123da | ||
|
|
ab03aeb7a0 | ||
|
|
f6860b56b8 | ||
|
|
6bea425367 | ||
|
|
1b3379b613 |
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']
|
||||
46
README.md
46
README.md
@@ -8,23 +8,23 @@ 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
|
||||
- Allows basic group management
|
||||
- Supports desktop notifications
|
||||
- Binaries for Windows, Mac, Linux and RaspBerry Pi
|
||||
|
||||
### Caveats
|
||||
|
||||
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.
|
||||
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 proper connection drop handling
|
||||
- No uploading of images/video/audio/data
|
||||
- Not configurable at all (except through your terminal settings)
|
||||
- Leaves its config files in your home folder
|
||||
- No auto-reconnect when connection drops
|
||||
- No automation of messages, no sending of messages through shell commands
|
||||
- FaceBook obviously doesn't endorse or like these kinds of apps and they're likely to break when FaceBook changes stuff in their web app
|
||||
|
||||
## Installation / Usage
|
||||
@@ -38,11 +38,11 @@ Always fresh, always up to date.
|
||||
- Download a release
|
||||
- Put the binary in your PATH (optional)
|
||||
- Run with `whatscli` (or double-click)
|
||||
- Scan the QR code with WhatsApp on your phone (maybe resize shell)
|
||||
- Scan the QR code with WhatsApp on your phone (resize shell or change font size to see whole code)
|
||||
|
||||
### Package Managers
|
||||
|
||||
Some unofficial ways to install via package managers are supported but the installed version might be out of date.
|
||||
Some ways to install via package managers are supported but the installed version might be out of date.
|
||||
|
||||
#### MacOS (homebrew)
|
||||
|
||||
@@ -50,8 +50,24 @@ Some unofficial ways to install via package managers are supported but the insta
|
||||
|
||||
#### Arch Linux (AUR)
|
||||
|
||||
- `git clone https://aur.archlinux.org/whatscli.git`
|
||||
- `cd whatscli`
|
||||
- `makepkg -si`
|
||||
- `yay -S whatscli`
|
||||
- `https://aur.archlinux.org/packages/whatscli/`
|
||||
|
||||
## Development
|
||||
|
||||
This app started as my first attempt at writing something in go. Some areas that are marked with `TODO` can still be improved but work mostly. If you want to contribute features or improve the code thats great, send a PR and we can discuss.
|
||||
|
||||
### Building
|
||||
|
||||
Using a recent version of go, building should be straightforward. Either use `go build`, `go run` etc. or use the included Makefile.
|
||||
|
||||
### Structure Overview
|
||||
|
||||
The `main.go` contains most UI elements which are based around a tview app running on the main routine. It uses a keymap configuration based on the tslocum/cbind library. Apart from that it mostly manages the selection of messages in the current chat as well as displaying the messages and chat list that the session manager sends.
|
||||
|
||||
The `messages/session_manager.go` runs a separate go routine to receive messages from the Rhymen/go-whatsapp library which in turn runs the websocket connection to the whatsapp server. The session manager receives the messages from go-whatsapp and the commands from the UI via channels that it drains on its main routine. It then updates the UI accordingly using the UiMessageHandler interface. This ensures "thread safe" management of the connection and data while both UI and network connection run separately.
|
||||
|
||||
Session manager is designed "object like", the MessageDatabase in `messages/storage.go` is similar and somewhat linked to the session manager. In theory the session manager could be run multiple times (multiple accounts) or a different implementation of a session manager could connect to a different service like e.g. Telegram.
|
||||
|
||||
In `messages/messages.go` most interfaces and data structures for communication are kept.
|
||||
|
||||
The `config/settings.go` keeps a singleton `Config` struct with the config that is loaded via the gopkg.in/ini.v1 library when the app starts. This makes it easy to quickly add new configuration items with default values that can be used across the app.
|
||||
|
||||
177
config/settings.go
Normal file
177
config/settings.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var configFilePath string
|
||||
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
|
||||
Copyuser string
|
||||
Pasteuser 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
|
||||
ForwardedText 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",
|
||||
Copyuser: "Ctrl+c",
|
||||
Pasteuser: "Ctrl+v",
|
||||
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",
|
||||
ForwardedText: "purple",
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
//TODO: only save if changes
|
||||
//newCfg := ini.Empty()
|
||||
//if err = ini.ReflectFromWithMapper(newCfg, &Config, ini.TitleUnderscore); err == nil {
|
||||
//err = newCfg.SaveTo(configFilePath)
|
||||
//}
|
||||
} else {
|
||||
cfg = ini.Empty()
|
||||
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 {
|
||||
fmt.Printf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigFilePath() string {
|
||||
return configFilePath
|
||||
}
|
||||
|
||||
func GetSessionFilePath() string {
|
||||
if sessionFilePath, err := xdg.ConfigFile("whatscli/session"); err == nil {
|
||||
return sessionFilePath
|
||||
}
|
||||
return GetHomeDir() + ".whatscli.session"
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
11
go.mod
11
go.mod
@@ -4,7 +4,10 @@ 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
|
||||
@@ -13,8 +16,12 @@ 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
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
github.com/zyedidia/clipboard v1.0.3
|
||||
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
|
||||
)
|
||||
|
||||
52
go.sum
52
go.sum
@@ -8,14 +8,26 @@ github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/g
|
||||
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
|
||||
github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4=
|
||||
github.com/adrg/xdg v0.2.3/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/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/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/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=
|
||||
@@ -34,11 +46,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/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=
|
||||
@@ -50,23 +69,35 @@ 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=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893 h1:24As98PZlIdjZn6V4wUulAbYlG7RPg/du9A1FZdT/vs=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893/go.mod h1:0ha5CGekam8ZV1kxkBxSlh7gfQ7YolUj2P/VruwH0QY=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/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=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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=
|
||||
github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
|
||||
github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||
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=
|
||||
@@ -89,11 +120,11 @@ 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=
|
||||
@@ -124,5 +155,14 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/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=
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
)
|
||||
|
||||
var contacts map[string]string
|
||||
var connection *whatsapp.Conn
|
||||
|
||||
// loads custom contacts from disk
|
||||
func LoadContacts() {
|
||||
contacts = make(map[string]string)
|
||||
file, err := os.Open(GetHomeDir() + ".whatscli.contacts")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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.Create(GetHomeDir() + ".whatscli.contacts")
|
||||
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)
|
||||
}
|
||||
78
messages/messages.go
Normal file
78
messages/messages.go
Normal file
@@ -0,0 +1,78 @@
|
||||
//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
|
||||
Forwarded 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,50 +2,218 @@ package messages
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rivo/tview"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gen2brain/beeep"
|
||||
"github.com/normen/whatscli/config"
|
||||
"github.com/normen/whatscli/qrcode"
|
||||
"github.com/rivo/tview"
|
||||
"mvdan.cc/xurls/v2"
|
||||
)
|
||||
|
||||
var textView *tview.TextView
|
||||
// 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 notify to UI
|
||||
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
|
||||
if int64(msg.Info.Timestamp) > sm.lastSent.Unix()+config.Config.General.NotificationTimeout {
|
||||
sm.db.NewUnreadChat(msg.Info.RemoteJid)
|
||||
if config.Config.General.EnableNotifications && !msg.Info.FromMe {
|
||||
err := beeep.Notify(sm.db.GetIdShort(msg.Info.RemoteJid), msg.Text, "")
|
||||
if err != nil {
|
||||
sm.uiHandler.PrintError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// notify if message is younger than 30 sec and not in focus
|
||||
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
|
||||
sm.db.NewUnreadChat(msg.Info.RemoteJid)
|
||||
if config.Config.General.EnableNotifications && !msg.Info.FromMe {
|
||||
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 {
|
||||
func (sm *SessionManager) getConnection() *whatsapp.Conn {
|
||||
var wac *whatsapp.Conn
|
||||
if connection == nil {
|
||||
wacc, err := whatsapp.NewConn(5 * time.Second)
|
||||
if sm.connection == nil {
|
||||
options := &whatsapp.Options{
|
||||
Timeout: 5 * time.Second,
|
||||
LongClientName: "WhatsCLI Client",
|
||||
ShortClientName: "whatscli",
|
||||
}
|
||||
wacc, err := whatsapp.NewConnWithOptions(options)
|
||||
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 {
|
||||
if connection != nil && connection.GetConnected() {
|
||||
connection.Disconnect()
|
||||
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()
|
||||
@@ -60,7 +228,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)
|
||||
@@ -74,21 +242,720 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving session: %v\n", err)
|
||||
}
|
||||
//<-time.After(3 * time.Second)
|
||||
//get initial battery state
|
||||
sm.BatteryChannel <- BatteryMsg{
|
||||
wac.Info.Battery,
|
||||
wac.Info.Plugged,
|
||||
false,
|
||||
}
|
||||
sm.StatusChannel <- StatusMsg{true, nil}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout logs out the user.
|
||||
func Logout() error {
|
||||
// disconnects the session
|
||||
func (sm *SessionManager) disconnect() error {
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
if wac != nil && wac.GetConnected() {
|
||||
_, err = wac.Disconnect()
|
||||
}
|
||||
sm.StatusChannel <- StatusMsg{false, err}
|
||||
return err
|
||||
}
|
||||
|
||||
// logout logs out the user, deletes session file
|
||||
func (ub *SessionManager) logout() error {
|
||||
err := ub.getConnection().Logout()
|
||||
ub.StatusChannel <- StatusMsg{false, err}
|
||||
ub.uiHandler.PrintText("removing login data..")
|
||||
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)
|
||||
}
|
||||
}
|
||||
chat.Unread = 0
|
||||
sm.db.chats[sm.currentReceiver] = chat
|
||||
sm.uiHandler.SetChats(sm.db.GetChatIds())
|
||||
}
|
||||
} 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 strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.LeaveGroup(groupId)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("left group " + groupId)
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "create":
|
||||
if !checkParam(command.Params, 1) {
|
||||
sm.printCommandUsage("create", "[user-id[] [user-id[] New Group Subject")
|
||||
sm.printCommandUsage("create", "New Group Subject")
|
||||
return
|
||||
}
|
||||
// first params are users if ending in CONTACTSUFFIX, rest is name
|
||||
users := []string{}
|
||||
idx := 0
|
||||
size := len(command.Params)
|
||||
for idx = 0; idx < size && strings.Index(command.Params[idx], CONTACTSUFFIX) > 0; idx++ {
|
||||
users = append(users, command.Params[idx])
|
||||
}
|
||||
name := ""
|
||||
if len(command.Params) > idx {
|
||||
name = strings.Join(command.Params[idx:], " ")
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
var groupId <-chan string
|
||||
groupId, err = wac.CreateGroup(name, users)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("creating new group " + name)
|
||||
resultInfo := <-groupId
|
||||
//{"status":200,"gid":"491600000009-0606000436@g.us","participants":[{"491700000000@c.us":{"code":"200"}},{"4917600000001@c.us":{"code": "200"}}]}
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(resultInfo), &result)
|
||||
newChatId := result["gid"].(string)
|
||||
sm.uiHandler.PrintText("got new Id " + newChatId)
|
||||
newChat := Chat{}
|
||||
newChat.Id = newChatId
|
||||
newChat.Name = name
|
||||
newChat.IsGroup = true
|
||||
sm.db.chats[newChatId] = newChat
|
||||
sm.uiHandler.SetChats(sm.db.GetChatIds())
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "add":
|
||||
groupId := sm.currentReceiver
|
||||
if strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
if !checkParam(command.Params, 1) {
|
||||
sm.printCommandUsage("add", "[user-id[]")
|
||||
return
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.AddMember(groupId, command.Params)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("added new members for " + groupId)
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "remove":
|
||||
groupId := sm.currentReceiver
|
||||
if strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
if !checkParam(command.Params, 1) {
|
||||
sm.printCommandUsage("remove", "[user-id[]")
|
||||
return
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.RemoveMember(groupId, command.Params)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("removed from " + groupId)
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "removeadmin":
|
||||
groupId := sm.currentReceiver
|
||||
if strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
if !checkParam(command.Params, 1) {
|
||||
sm.printCommandUsage("removeadmin", "[user-id[]")
|
||||
return
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.RemoveAdmin(groupId, command.Params)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("removed admin for " + groupId)
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "admin":
|
||||
groupId := sm.currentReceiver
|
||||
if strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
if !checkParam(command.Params, 1) {
|
||||
sm.printCommandUsage("admin", "[user-id[]")
|
||||
return
|
||||
}
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.SetAdmin(groupId, command.Params)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("added admin for " + groupId)
|
||||
}
|
||||
sm.uiHandler.PrintError(err)
|
||||
case "subject":
|
||||
groupId := sm.currentReceiver
|
||||
if strings.Index(groupId, GROUPSUFFIX) < 0 {
|
||||
sm.uiHandler.PrintText("not a group")
|
||||
return
|
||||
}
|
||||
if !checkParam(command.Params, 1) || groupId == "" {
|
||||
sm.printCommandUsage("subject", "new-subject -> in group chat")
|
||||
return
|
||||
}
|
||||
name := strings.Join(command.Params, " ")
|
||||
wac := sm.getConnection()
|
||||
var err error
|
||||
_, err = wac.UpdateGroupSubject(name, groupId)
|
||||
if err == nil {
|
||||
sm.uiHandler.PrintText("updated subject for " + groupId)
|
||||
}
|
||||
newChat := sm.db.chats[groupId]
|
||||
newChat.Name = name
|
||||
sm.db.chats[groupId] = newChat
|
||||
sm.uiHandler.SetChats(sm.db.GetChatIds())
|
||||
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
|
||||
newMsg.Forwarded = msg.ContextInfo.IsForwarded
|
||||
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()
|
||||
newid, err := sm.getConnection().Send(msg)
|
||||
msg.Info.Id = newid
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *SessionManager) HandleJsonMessage(message string) {
|
||||
//sm.uiHandler.PrintText(message)
|
||||
}
|
||||
|
||||
// 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(GetHomeDir() + ".whatscli.session")
|
||||
file, err := os.Open(config.GetSessionFilePath())
|
||||
if err != nil {
|
||||
return session, err
|
||||
// load old session file, delete if found
|
||||
file, err = os.Open(config.GetHomeDir() + ".whatscli.session")
|
||||
if err != nil {
|
||||
return session, err
|
||||
} else {
|
||||
os.Remove(config.GetHomeDir() + ".whatscli.session")
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
@@ -101,7 +968,7 @@ func readSession() (whatsapp.Session, error) {
|
||||
|
||||
// saves the session file to disk
|
||||
func writeSession(session whatsapp.Session) error {
|
||||
file, err := os.Create(GetHomeDir() + ".whatscli.session")
|
||||
file, err := os.Create(config.GetSessionFilePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,5 +983,5 @@ func writeSession(session whatsapp.Session) error {
|
||||
|
||||
// deletes the session file from disk
|
||||
func removeSession() error {
|
||||
return os.Remove(GetHomeDir() + ".whatscli.session")
|
||||
return os.Remove(config.GetSessionFilePath())
|
||||
}
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
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
|
||||
contacts map[string]Contact
|
||||
chats map[string]Chat
|
||||
}
|
||||
|
||||
// initialize the database
|
||||
func (db *MessageDatabase) Init() {
|
||||
//var this = *db
|
||||
(*db).textMessages = make(map[string][]*whatsapp.TextMessage)
|
||||
(*db).messagesById = make(map[string]*whatsapp.TextMessage)
|
||||
(*db).otherMessages = make(map[string]*interface{})
|
||||
(*db).latestMessage = make(map[string]uint64)
|
||||
db.textMessages = make(map[string][]*whatsapp.TextMessage)
|
||||
db.messagesById = make(map[string]*whatsapp.TextMessage)
|
||||
db.otherMessages = make(map[string]*interface{})
|
||||
db.latestMessage = make(map[string]uint64)
|
||||
db.contacts = make(map[string]Contact)
|
||||
db.chats = make(map[string]Chat)
|
||||
}
|
||||
|
||||
// add a text message to the database, stored by RemoteJid
|
||||
@@ -37,23 +32,45 @@ func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool {
|
||||
//var this = *db
|
||||
var didNew = false
|
||||
var wid = msg.Info.RemoteJid
|
||||
if (*db).textMessages[wid] == nil {
|
||||
if db.textMessages[wid] == nil {
|
||||
var newArr = []*whatsapp.TextMessage{}
|
||||
(*db).textMessages[wid] = newArr
|
||||
(*db).latestMessage[wid] = msg.Info.Timestamp
|
||||
db.textMessages[wid] = newArr
|
||||
db.latestMessage[wid] = msg.Info.Timestamp
|
||||
didNew = true
|
||||
} else if (*db).latestMessage[wid] < msg.Info.Timestamp {
|
||||
(*db).latestMessage[wid] = msg.Info.Timestamp
|
||||
} else if db.latestMessage[wid] < msg.Info.Timestamp {
|
||||
db.latestMessage[wid] = msg.Info.Timestamp
|
||||
didNew = true
|
||||
}
|
||||
(*db).textMessages[wid] = append((*db).textMessages[wid], msg)
|
||||
(*db).messagesById[msg.Info.Id] = msg
|
||||
sort.Slice((*db).textMessages[wid], func(i, j int) bool {
|
||||
return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp
|
||||
})
|
||||
//do we know this chat? if not add
|
||||
if _, ok := db.chats[msg.Info.RemoteJid]; !ok {
|
||||
//don't have this chat!
|
||||
isGroup := strings.Contains(msg.Info.RemoteJid, GROUPSUFFIX)
|
||||
db.chats[msg.Info.RemoteJid] = Chat{
|
||||
msg.Info.RemoteJid,
|
||||
isGroup,
|
||||
db.GetIdName(msg.Info.RemoteJid),
|
||||
1,
|
||||
int64(msg.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
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) NewUnreadChat(id string) {
|
||||
if chat, ok := db.chats[id]; ok {
|
||||
chat.Unread++
|
||||
db.chats[id] = chat
|
||||
}
|
||||
}
|
||||
|
||||
// add audio/video/image/doc message, stored by message id
|
||||
func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
var id = ""
|
||||
@@ -69,31 +86,63 @@ func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
id = v.Info.Id
|
||||
}
|
||||
if id != "" {
|
||||
(*db).otherMessages[id] = msg
|
||||
db.otherMessages[id] = msg
|
||||
}
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) AddContact(contact Contact) {
|
||||
db.contacts[contact.Id] = contact
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) AddChat(chat Chat) {
|
||||
db.chats[chat.Id] = chat
|
||||
}
|
||||
|
||||
// get an array of all chat ids
|
||||
func (db *MessageDatabase) GetContactIds() []string {
|
||||
//var this = *db
|
||||
keys := make([]string, len((*db).textMessages))
|
||||
func (db *MessageDatabase) GetChatIds() []Chat {
|
||||
keys := make([]Chat, len(db.chats))
|
||||
i := 0
|
||||
for k := range (*db).textMessages {
|
||||
for _, k := range db.chats {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return (*db).latestMessage[keys[i]] > (*db).latestMessage[keys[j]]
|
||||
return db.latestMessage[keys[i].Id] > db.latestMessage[keys[j].Id]
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
// gets a pretty name for a whatsapp id
|
||||
func (sm *MessageDatabase) GetIdName(id string) string {
|
||||
if val, ok := sm.contacts[id]; ok {
|
||||
if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Short != "" {
|
||||
return val.Short
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
|
||||
}
|
||||
|
||||
// gets a short name for a whatsapp id
|
||||
func (sm *MessageDatabase) GetIdShort(id string) string {
|
||||
if val, ok := sm.contacts[id]; ok {
|
||||
//TODO val.notify from whatsapp??
|
||||
if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Name != "" {
|
||||
return val.Name
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(strings.TrimSuffix(id, CONTACTSUFFIX), GROUPSUFFIX)
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) GetMessageInfo(id string) string {
|
||||
if _, ok := (*db).otherMessages[id]; ok {
|
||||
if _, ok := db.otherMessages[id]; ok {
|
||||
return "[yellow]OtherMessage[-]"
|
||||
}
|
||||
out := ""
|
||||
if msg, ok := (*db).messagesById[id]; ok {
|
||||
if msg, ok := db.messagesById[id]; ok {
|
||||
out += "[yellow]ID: " + msg.Info.Id + "[-]\n"
|
||||
out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n"
|
||||
out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n"
|
||||
@@ -105,120 +154,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) {
|
||||
//var this = *db
|
||||
var out = ""
|
||||
var arr = []string{}
|
||||
for _, element := range (*db).textMessages[wid] {
|
||||
out += GetTextMessageString(element)
|
||||
out += "\n"
|
||||
arr = append(arr, element.Info.Id)
|
||||
func (db *MessageDatabase) GetMessages(wid string) []whatsapp.TextMessage {
|
||||
var arr = []whatsapp.TextMessage{}
|
||||
for _, element := range db.textMessages[wid] {
|
||||
arr = append(arr, *element)
|
||||
}
|
||||
return out, arr
|
||||
}
|
||||
|
||||
// create a formatted string with regions based on message ID from a text message
|
||||
func GetTextMessageString(msg *whatsapp.TextMessage) string {
|
||||
out := ""
|
||||
text := tview.Escape((*msg).Text)
|
||||
tim := time.Unix(int64((*msg).Info.Timestamp), 0)
|
||||
out += "[\""
|
||||
out += (*msg).Info.Id
|
||||
out += "\"]"
|
||||
if (*msg).Info.FromMe { //msg from me
|
||||
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [blue::b]Me: [-::-]" + text
|
||||
} else if strings.Contains((*msg).Info.RemoteJid, GROUPSUFFIX) { // group msg
|
||||
userId := (*msg).Info.SenderJid
|
||||
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort(userId) + ": [-::-]" + text
|
||||
} else { // message from others
|
||||
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort((*msg).Info.RemoteJid) + ": [-::-]" + text
|
||||
}
|
||||
out += "[\"\"]"
|
||||
return out
|
||||
}
|
||||
|
||||
// load data for message specified by message id TODO: support types
|
||||
func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
|
||||
if msg, ok := (*db).otherMessages[wid]; ok {
|
||||
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, open bool) (string, error) {
|
||||
if msg, ok := (*db).otherMessages[wid]; ok {
|
||||
var fileName string = GetHomeDir() + "Downloads" + 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, open)
|
||||
} 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, open)
|
||||
} 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, open)
|
||||
} 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, open)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("No attachments found")
|
||||
}
|
||||
|
||||
// helper to save an attachment and open it if specified
|
||||
func saveAttachment(data []byte, path string, openIt bool) (string, error) {
|
||||
err := ioutil.WriteFile(path, data, 0644)
|
||||
if err == nil {
|
||||
if openIt {
|
||||
open.Run(path)
|
||||
}
|
||||
} else {
|
||||
return path, err
|
||||
}
|
||||
return path, nil
|
||||
return arr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user