Compare commits

...

141 Commits

Author SHA1 Message Date
normen
ae25296b9d bump version 2021-11-02 19:06:17 +01:00
Normen Hansen
020ccfbb4b Merge pull request #55 from dgrisham/master
set go-whatsapp client version to fix login error
2021-11-02 19:04:10 +01:00
dgrisham
aa53f24c39 set go-whatsapp client version to fix login error
Based on https://github.com/Rhymen/go-whatsapp/issues/626#issuecomment-957082821
2021-11-02 09:26:18 -06:00
normen
b282213ca1 - possible fix for #48 2021-07-12 21:24:10 +02:00
normen
46f719c1f7 - bump go-whatsapp library version
- fixes #46
2021-06-23 11:01:43 +02:00
normen
309eca5d00 update tview/tcell dependencies
- fixes mouse selection of messages
2021-05-01 23:16:08 +02:00
normen
c2e7d5d6b7 - fix string combination
- bump version
2021-04-10 12:31:00 +02:00
Normen Hansen
88cc6b09ae Merge pull request #42 from cetinajero/feature/customizable-backlog-msg-qty
Setting for the # of messages per backlog
2021-04-10 12:24:17 +02:00
Edgar Tinajero
646fed75a6 Fix indentation 2021-04-09 12:39:18 -06:00
Edgar Tinajero
0b5476a1c8 Setting for the # of messages per backlog
This adds a customizable setting to define how many messages to retrieve per backlog cmd execution
2021-04-09 12:36:08 -06:00
normen
8510dd40ee add license info 2021-02-20 17:03:12 +01:00
normen
ee5d675f98 bump version 2021-02-15 20:54:36 +01:00
Normen Hansen
2a3f53c4c3 Merge pull request #37 from keegancsmith/terminal-bell
add UseTerminalBell as an alternative to desktop notifications
2021-02-15 20:52:12 +01:00
Keegan Carruthers-Smith
cc71c296b2 add UseTerminalBell as an alternative to desktop notifications 2021-02-15 21:17:07 +02:00
normen
b98af9f0c7 document notifications 2020-12-08 16:59:51 +01:00
normen
4fdcd70703 cleanups 2020-12-07 23:02:44 +01:00
normen
f690d798bc more doc! 2020-12-07 22:54:09 +01:00
normen
dd6e4f54b1 Dr. Doc 🚑 2020-12-07 22:45:09 +01:00
normen
49718acc49 avoid trying to send message when no receiver is set
Fixes #26
2020-12-05 16:26:43 +01:00
normen
6a97d6dfe6 move more stuff to UI thread (image painting) 2020-11-30 19:45:58 +01:00
normen
0bacea701a fix own messages disappearing in UI 2020-11-29 19:05:36 +01:00
normen
421139730c don't save config on each start 2020-11-28 18:21:45 +01:00
normen
313b4606c5 remove unused variable/import 2020-11-28 15:44:51 +01:00
normen
f79e3e46b7 add development info 2020-11-28 15:37:09 +01:00
normen
3053d15929 add exit key binding to help screen 2020-11-28 15:33:26 +01:00
normen
73cd749394 move commands info into separate help screen 2020-11-28 15:28:49 +01:00
normen
8e8f2da43f allow paging when selecting messages 2020-11-28 14:56:58 +01:00
normen
e8d9f266fa allow pasting even if theres no system clipboard 2020-11-28 14:30:15 +01:00
normen
2763bb14b4 switch clipboard library 2020-11-28 04:04:06 +01:00
normen
f91ffbef0f colorize forwarded text differently 2020-11-28 02:24:32 +01:00
normen
3521a3b6f5 add /remove and /removeadmin commands 2020-11-28 00:23:20 +01:00
normen
a6d7954795 don't just print help when no messages are available 2020-11-27 23:23:36 +01:00
normen
96e9d75810 small fix in help screen 2020-11-27 21:48:04 +01:00
normen
b2904929b0 update README 2020-11-27 21:42:32 +01:00
normen
033e7aa1ac allow creating and managing groups 2020-11-27 21:33:40 +01:00
normen
53e404dd55 add command/binding to copy selected user id 2020-11-27 20:08:14 +01:00
normen
8318d2e80f remove redundant "reported an error" 2020-11-27 18:58:51 +01:00
normen
3af2a3738f fix unread count not updating 2020-11-27 14:22:20 +01:00
normen
e31c2c3e36 get the battery state on connection 2020-11-27 03:30:13 +01:00
normen
3f20981550 cleaner logout 2020-11-27 03:10:25 +01:00
normen
f8368e4998 add /read command to set chat to read
- display connection status (connecting, connected, disconnected)
- allow setting unread count color
2020-11-27 00:11:59 +01:00
normen
121a73c312 add unread count (no reset yet) 2020-11-26 22:52:47 +01:00
normen
86af0d82a4 remove contact file name getter 2020-11-26 21:50:41 +01:00
normen
b94129fb0e rename focus_contacts and contact_sidebar_width - sorry! 2020-11-26 21:48:59 +01:00
normen
59a843bb8d use chat and contact list reported by go-whatsapp
Fixes #22
2020-11-26 21:43:59 +01:00
normen
a86aa3eec3 show contact name for status messages 2020-11-26 18:53:22 +01:00
normen
a20b4e3592 update dependencies 2020-11-26 18:45:23 +01:00
normen
9e62295188 unify variable names 2020-11-26 02:46:00 +01:00
normen
2e891d05ca don't start session manager twice 2020-11-26 02:17:44 +01:00
normen
bfbec54de3 remove number suffix for groups in fallback 2020-11-26 02:10:28 +01:00
normen
c677bce14e fix contact/group color mixup in contact list 2020-11-26 02:05:24 +01:00
normen
6f30efeebe abstract messages, move message styling to UI 2020-11-26 01:27:48 +01:00
normen
14a0e74b25 use pointer for database 2020-11-25 21:34:27 +01:00
normen
4951ca24da help screen cleanups 2020-11-25 03:19:28 +01:00
normen
1cd6c25b02 fix preview and download path mixup 2020-11-25 03:11:38 +01:00
normen
d9a9e7f753 separate messages and interfaces to separate file 2020-11-25 02:12:57 +01:00
normen
5388a1b408 cleanup help 2020-11-25 01:38:06 +01:00
normen
7687af38e1 set file name when uploading 2020-11-24 23:16:29 +01:00
normen
9b26641a0c don't swallow error messages on upload/send 2020-11-24 22:53:36 +01:00
normen
e5ef667909 show help and set no receiver on contact root 2020-11-24 22:29:50 +01:00
normen
c39479f12b fix notifications for own messages 2020-11-24 22:28:24 +01:00
normen
f1109f6465 add /colorlist command 2020-11-24 21:05:12 +01:00
normen
127883701d fix version snafu in 0.9.1 release 2020-11-24 20:52:07 +01:00
normen
5cacc3c5ea bump version 2020-11-24 20:46:17 +01:00
normen
65957ff732 use color_negative for all error messages 2020-11-24 20:38:04 +01:00
normen
7a096dbc94 allow configuring command for image to text conversion 2020-11-24 20:25:21 +01:00
normen
a7977959b5 help info update 2020-11-24 19:19:51 +01:00
normen
ea92d56426 allow sending image, video and audio messages
files have to be right format to be recognized by WhatsApp
2020-11-24 19:16:37 +01:00
normen
3c193d219e allow opening URLs in messages
Fixes #19
2020-11-24 18:47:58 +01:00
normen
f3a2bd3e88 add some code documentation 2020-11-24 17:37:07 +01:00
normen
f0488851ae remove local contacts feature creep
remove singleton connection
2020-11-24 17:30:06 +01:00
normen
c5276247b8 avoid sending notifications when loading backlog 2020-11-24 14:20:26 +01:00
normen
27d3a48d98 update readme 2020-11-24 14:03:52 +01:00
normen
9065248d1c add notification support 2020-11-24 13:58:31 +01:00
normen
6e0c150e26 fix creating file suffix for download 2020-11-24 13:03:47 +01:00
normen
489b23899e allow uploading of files as documents 2020-11-24 05:17:22 +01:00
normen
8f50aa02d6 visual fixes 2020-11-24 04:34:03 +01:00
normen
c3454e734f fix re-creating defaults 2020-11-24 04:22:34 +01:00
normen
2138e671c4 update dependecies 2020-11-24 02:47:31 +01:00
normen
0b6816f6e3 parse env variables in config 2020-11-24 02:46:28 +01:00
normen
b72a5e0cc6 add status bar, use config struct 2020-11-24 01:39:34 +01:00
normen
e4f1851b50 cleanup settings 2020-11-24 01:38:29 +01:00
normen
20f879271c fix config reset - still defaults don't come back 2020-11-24 00:31:00 +01:00
normen
d60e652d17 make default settings re-appear when deleted 2020-11-23 23:43:30 +01:00
normen
6f0d93e29e fix double default keymapping 2020-11-23 22:23:20 +01:00
normen
d7f8f0a918 allow revoking messages, allow leaving groups 2020-11-23 18:55:17 +01:00
normen
8d89a2a7f4 fix send command when called directly 2020-11-23 18:03:21 +01:00
normen
0e4a694d09 small cleanups 2020-11-23 17:18:33 +01:00
normen
754420e0c7 don't become unresponsive when login fails on startup 2020-11-23 17:15:04 +01:00
normen
ae29e13108 allow command parameters 2020-11-23 17:11:37 +01:00
normen
0e3811de44 print login errors 2020-11-23 16:48:19 +01:00
normen
d1c0e870f0 allow specifying command prefix, unify command calls 2020-11-23 16:40:44 +01:00
normen
795d8f7e63 show path again when downloading files 2020-11-23 14:43:18 +01:00
normen
5008ca46d8 fix info command, separate UI from connection 2020-11-23 14:28:11 +01:00
normen
e03e261d00 decouple UI from messaging library 2020-11-23 13:28:18 +01:00
normen
4a119a700f combine internal communication channels 2020-11-23 13:05:05 +01:00
normen
c1897b475a allow backlog to be read, improve code separation 2020-11-23 04:44:42 +01:00
normen
1672b42f7e avoid storing messages twice 2020-11-22 21:35:04 +01:00
normen
8a28ea47fd fix fallback value for contacts path 2020-11-22 17:20:07 +01:00
normen
3450fbc78f allow configuring input background and text color separately 2020-11-22 16:22:13 +01:00
normen
48ad9ce669 update links 2020-11-22 15:18:32 +01:00
normen
fd676f13cf - fix text input text color not being set
- Fixes #20
2020-11-22 15:02:38 +01:00
normen
63b5f3a604 update links 2020-11-22 14:54:53 +01:00
normen
b20031ff6a update links 2020-11-22 14:46:51 +01:00
normen
00516c3191 update links 2020-11-22 14:34:51 +01:00
Normen Hansen
76c4010ce2 Create FUNDING.yml 2020-11-22 14:03:54 +01:00
normen
0b8d265024 remove old keybindings 2020-11-22 13:21:38 +01:00
normen
80825b0dff small cleanups 2020-11-22 13:17:27 +01:00
normen
42daf1a9f7 add configuration system 2020-11-22 04:35:03 +01:00
normen
04960123da fix freeze when downloading messages 2020-11-21 21:13:38 +01:00
normen
ab03aeb7a0 error output and README improvements 2020-11-20 14:40:01 +01:00
normen
f6860b56b8 secure connection handling with mutex 2020-11-20 14:31:17 +01:00
normen
6bea425367 make message database thread safe 2020-11-20 14:10:09 +01:00
normen
1b3379b613 learning GO - using its sugar 2020-11-20 13:24:15 +01:00
normen
1cc68c9203 fix /connect behavior 2020-11-20 00:05:22 +01:00
normen
c3a6f98b13 make /connect command work, revert in-memory images 2020-11-19 23:47:43 +01:00
normen
21fec5a5d0 don't re-download stuff 2020-11-19 23:42:35 +01:00
normen
b2b798c514 only load messages to memory for inline display 2020-11-19 20:38:02 +01:00
normen
5d63cf41eb improve README layout 2020-11-19 18:01:01 +01:00
normen
e519c4a892 small fix in release script 2020-11-19 17:10:09 +01:00
normen
754106de62 fix last messages in open chat not being selectable 2020-11-19 17:05:21 +01:00
normen
07474c5b3d improve release script 2020-11-19 16:49:29 +01:00
normen
b142aef93c add homebrew tap update to release script 2020-11-19 16:40:59 +01:00
normen
babe81c1aa add info about package managers 2020-11-19 14:11:01 +01:00
normen
b89ecfec79 add screenshot 2020-11-19 12:36:53 +01:00
normen
5e7659b845 clean/update dependencies 2020-11-19 12:28:56 +01:00
normen
eee49d713d upadate docs, use color in jp2a 2020-11-19 04:40:43 +01:00
normen
2a6b0efe68 always scroll back chat view when exiting 2020-11-19 04:28:35 +01:00
normen
9ee28a011c improve focus and highlight handling 2020-11-19 04:03:51 +01:00
normen
16a6519ba7 add s key to help 2020-11-19 03:05:59 +01:00
normen
bb00a9a8f0 allow displaying images using jp2a 2020-11-19 03:04:22 +01:00
normen
4ef8b76a55 report full path on download 2020-11-19 02:59:01 +01:00
normen
6a832cbcf3 reduce data copying 2020-11-19 01:08:22 +01:00
normen
c2f3a012dc remove highlights when pressing <Esc> or o/d 2020-11-19 00:24:27 +01:00
normen
147feb90ec update README 2020-11-19 00:04:48 +01:00
normen
b39d4891e0 fix inverted logic for selecting messages 2020-11-19 00:02:21 +01:00
normen
19455b0baa add some code documentation 2020-11-18 23:58:28 +01:00
normen
a37e6c7d3b fix selection when highlight is not visible 2020-11-18 23:36:13 +01:00
normen
82d2a6637d allow navigating chat window selection 2020-11-18 23:30:02 +01:00
normen
1eb3af823e move session_manager to messages package 2020-11-18 22:53:00 +01:00
normen
773f9b783d add support for opening attachments 2020-11-18 22:39:53 +01:00
13 changed files with 2483 additions and 627 deletions

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

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

128
README.md
View File

@@ -2,60 +2,116 @@
A command line interface for whatsapp, based on [go-whatsapp](https://github.com/Rhymen/go-whatsapp) and [tview](https://github.com/rivo/tview)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ WhatsCLI v0.4.2 Help: /name NewName | /addname 123456 NewName | /quit |│
├──────────────────────────────┬──────────────────────────────────────────┤
│Contacts │(03-14-12 22:59:00) Me: Hey, whatscli here│
│├──Peter │(03-14-12 23:00:00) Peter: Cool 😀 │
│├──Paul │ │
│└──Mary │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ ├──────────────────────────────────────────┤
│ │Yeah, love the shell! │
└──────────────────────────────┴──────────────────────────────────────────┘
```
![whatscli-screenshot](/doc/screenshot.png?raw=true "WhatsCLI 0.6.5")
## Features
Things that work.
- Allows sending and receiving WhatsApp messages in a command line app
- Connects through the Web App API without browser
- Sending and receiving WhatsApp messages in a command line app
- Connects through the Web App API without a browser
- 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. 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 support for images, videos, documents etc.
- No incoming message notification / count
- No proper connection drop handling
- Not configurable at all
- 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
## Installation
How to get it running and how to use it
### Latest Release
Always fresh, always up to date.
- Download a release
- Put the binary in your PATH (optional)
- Run with `whatscli` (or double-click)
- Scan 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 ways to install via package managers are supported but the installed version might be out of date.
#### MacOS (homebrew)
- `brew install normen/tap/whatscli`
#### Arch Linux (AUR)
- `https://aur.archlinux.org/packages/whatscli/`
## Usage
Most information, all commands and key bindings are availabe through the in-app help, simply type `/help` and/or `/commands`.
### Login
When starting up, whatscli will immediately try to connect to the WhatsApp server to log in. Keep your phone ready to scan the appearing QR code in WhatsApp on your Phone. If you don't manage to scan the code quick enough just restart the application. If you can not see the whole QR code, reduce the font size of your terminal or increase the window size.
After scanning the QR code the chats should be populated. After you have done this once, whatscli will be able to log into WhatsApp automatically on start. To log out of WhapsApp completely type `/logout`.
### Messaging / Commands
Select a chat on the left and start typing in the input field at the bottom to send messages. Switch between the chat list and the input fiel with `<Tab>`.
For issuing commands the same input field is used. By default commands are prefixed with `/`. You can for example use the `/sendimage /path/to/file.jpg` command to send images, see `/help` for more commands.
When paths are given for commands you don't need to surround the path in quotes, even if it contains spaces. Also don't prefix spaces with backslashes (as the copy-paste function of MacOS does for example).
### Messages
When pressing `Ctrl-w` (default mapping) you enter "message selection mode" which allows selecting a single message and performing operations on them. For example pressing `o` while a message is selected allows opening any attachments through an external application.
#### Image display
You can display images in whatscli using external programs that convert the image to UTF characters. I found that `jp2a` works well for jpeg images, it is available through package managers on most systems. However the "image quality" leaves a lot to be desired. The [PIXterm](https://github.com/eliukblau/pixterm) app allows displaying true-color versions of the images which are quite recognizable already.
To configure the used command and its parameters edit the `show_command` parameter in `whatscli.config`, see `/help` for the config file location.
#### Copy-Pasting User IDs
Some commands such as the `/add` and `/remove` require a "user id" as their input. You can copy the user ID of a selected chat or a selected message to the clipboard with `Ctrl-c` (default mapping) and easily append them to the current input using `Ctrl-v`.
### Notifications
The app supports basic desktop notifications through the `gen2brain/beeep` library, to enable it set `enable_notifications = true` in `whatscli.config`. Set `use_terminal_bell = true` to ring your terminal's bell instead of sending a desktop notification.
### Configuration
Most key bindings, colors and other options can be configured in the `whatscli.config` file, the `/help` command shows its location.
## 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.
## License
This software is released under MIT license. Remember that this gives you all freedom except for slapping your name on it.

181
config/settings.go Normal file
View File

@@ -0,0 +1,181 @@
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
UseTerminalBell bool
NotificationTimeout int64
BacklogMsgQuantity int
}
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,
UseTerminalBell: false,
NotificationTimeout: 60,
BacklogMsgQuantity: 10,
},
&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)
}

BIN
doc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

35
go.mod
View File

@@ -3,17 +3,34 @@ module github.com/normen/whatscli
go 1.15
require (
github.com/Rhymen/go-whatsapp v0.1.1-0.20201117204225-79ad714fa46a
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
github.com/golang/protobuf v1.4.3 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
code.rocketnine.space/tslocum/cbind v0.1.5
github.com/Rhymen/go-whatsapp v0.1.2-0.20210615184944-2b8a3e9b8aa2
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/adrg/xdg v0.3.3
github.com/gabriel-vasile/mimetype v1.3.0
github.com/gdamore/tcell/v2 v2.3.11
github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gopherjs/gopherjs v0.0.0-20210621113107-84c6004145de // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893
github.com/rivo/tview v0.0.0-20210608105643-d4fb0348227b
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 // indirect
golang.org/x/text v0.3.4 // indirect
google.golang.org/protobuf v1.25.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/zyedidia/clipboard v1.0.3
gitlab.com/tslocum/cbind v0.1.4
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
gopkg.in/ini.v1 v1.62.0
mvdan.cc/xurls/v2 v2.2.0
)

397
go.sum
View File

@@ -1,43 +1,103 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE=
code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp v0.1.0 h1:XTXhFIQ/fx9jKObUnUX2Q+nh58EyeHNhX7DniE8xeuA=
github.com/Rhymen/go-whatsapp v0.1.0/go.mod h1:xJSy+okeRjKkQEH/lEYrnekXB3PG33fqL0I6ncAkV50=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a h1:LW+rX0NY6LzMPa2hJcgmQlfiFJUihzOMAaIoCq+P3xc=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201117204225-79ad714fa46a h1:IhYU0cQ0cvMO9MOuOO08X8zUtMQ+AVIV0D+7eZVtHe8=
github.com/Rhymen/go-whatsapp v0.1.1-0.20201117204225-79ad714fa46a/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp v0.1.1 h1:OK+bCugQcr2YjyYKeDzULqCtM50TPUFM6LvQtszKfcw=
github.com/Rhymen/go-whatsapp v0.1.1/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp v0.1.2-0.20210615184944-2b8a3e9b8aa2 h1:xEhWgQl3hCOtFph6nWpTjVgAdPmAk0zUTENPDb3HJ7Y=
github.com/Rhymen/go-whatsapp v0.1.2-0.20210615184944-2b8a3e9b8aa2/go.mod h1:DNSFRLFDFIqm2+0aJzSOVfn25020vldM4SRqz6YtLgI=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
github.com/SchulteMK/go-whatsapp v0.0.0-20201117193111-50e7347bfbb6 h1:Rsit49rPlurIGzQn9VGOPph0pNjUX+bo+9Lg51D8rn0=
github.com/SchulteMK/go-whatsapp v0.0.0-20201117193111-50e7347bfbb6/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
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/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
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/gabriel-vasile/mimetype v1.3.0 h1:4YOHITFLyYwF+iqG0ybSLGArRItynpfwdlWRmJnd75E=
github.com/gabriel-vasile/mimetype v1.3.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
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 h1:GRWG8aLfWAlekj9Q6W29bVvkHENc6hp79XOqG4AWDOs=
github.com/gdamore/tcell/v2 v2.0.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
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/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.2.1 h1:Gt8wk0jd5pIK2CyXNo/fqwxNWf726j1lQjEDdfbnqTc=
github.com/gdamore/tcell/v2 v2.2.1/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.3.3/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.3.11 h1:ECO6WqHGbKZ3HrSL7bG/zArMCmLaNr5vcjjMVnLHpzc=
github.com/gdamore/tcell/v2 v2.3.11/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
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/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1 h1:Xh9mvwEmhbdXlRSsgn+N0zj/NqnKvpeqL08oKDHln2s=
github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -47,118 +107,349 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/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/gopherjs v0.0.0-20210621113107-84c6004145de h1:q+tIagAgwJBQPaeOPvis2b+cxfaZ5HNkzoctl7SMxDQ=
github.com/gopherjs/gopherjs v0.0.0-20210621113107-84c6004145de/go.mod h1:MtKwTfDNYAP5EtbQSMYjTSqvj1aXJKQRASWq3bwaP+g=
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 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
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 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743 h1:9BBjVJTRxuYBeCAv9DFH2hSzY0ujLx5sxMg5D3K/Xeg=
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743/go.mod h1:t7mcA3nlK9dxD1DMoz/DQRMWFMkGBUj6rJBM5VNfLFA=
github.com/rivo/tview v0.0.0-20201117185959-f9f2182520da h1:XUh+g7tjO81Ph5+7GSco4VLrBbQPnakm8M6sQU+fT5Y=
github.com/rivo/tview v0.0.0-20201117185959-f9f2182520da/go.mod h1:0ha5CGekam8ZV1kxkBxSlh7gfQ7YolUj2P/VruwH0QY=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
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.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/tview v0.0.0-20210427112837-09cec83b1732 h1:sY3Egm/ld9GcxAUs5zBPo1lEzC15ZteptMJmd8Ltc6Q=
github.com/rivo/tview v0.0.0-20210427112837-09cec83b1732/go.mod h1:Uajo+DndkMDGPzEw/Z92AgJrpx6Rvy2HRo/xP1YbXAI=
github.com/rivo/tview v0.0.0-20210608105643-d4fb0348227b h1:VRivPtgGaL9sjudoQUyHpKWJeDIFYUYbJ+AF03NEvLI=
github.com/rivo/tview v0.0.0-20210608105643-d4fb0348227b/go.mod h1:IxQujbYMAh4trWr0Dwa8jfciForjVmxyHpskZX6aydQ=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 h1:0WDrJ1E7UolDk1KhTXxxw3Fc8qtk5x7dHP431KHEJls=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582/go.mod h1:tCqSYrHVcf3i63Co2FzBkTCo2gdF6Zak62921dSfraU=
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk=
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 h1:s330+6z/Ko3J0o6rvOcwXe5nzs7UT9tLKHoOXYn6uE0=
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -167,10 +458,32 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

874
main.go
View File

@@ -1,59 +1,69 @@
package main
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
"time"
"github.com/Rhymen/go-whatsapp"
"code.rocketnine.space/tslocum/cbind"
"github.com/gdamore/tcell/v2"
"github.com/normen/whatscli/config"
"github.com/normen/whatscli/messages"
"github.com/rivo/tview"
"github.com/skratchdot/open-golang/open"
"github.com/zyedidia/clipboard"
)
type waMsg struct {
Wid string
Text string
}
var VERSION string = "v0.5.3"
var sendChannel chan waMsg
var textChannel chan whatsapp.TextMessage
var contactChannel chan whatsapp.Contact
var VERSION string = "v1.0.11"
var sndTxt string = ""
var currentReceiver 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 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() {
msgStore = messages.MessageDatabase{}
msgStore.Init()
messages.LoadContacts()
config.InitConfig()
uiHandler = UiHandler{}
sessionManager = &messages.SessionManager{}
sessionManager.Init(uiHandler)
app = tview.NewApplication()
sideBarWidth := config.Config.Ui.ChatSidebarWidth
gridLayout := tview.NewGrid()
gridLayout.SetRows(1, 0, 1)
gridLayout.SetColumns(30, 0, 30)
gridLayout.SetColumns(sideBarWidth, 0, sideBarWidth)
gridLayout.SetBorders(true)
gridLayout.SetBackgroundColor(tcell.ColorBlack)
gridLayout.SetBackgroundColor(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.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Type /help for help")
topBar.SetScrollable(false)
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])
//infoBar = tview.NewTextView()
//infoBar.SetDynamicColors(true)
//infoBar.SetText("🔋: ??%")
infoBar = tview.NewTextView()
infoBar.SetDynamicColors(true)
UpdateStatusBar(messages.SessionStatus{})
textView = tview.NewTextView().
SetDynamicColors(true).
@@ -62,25 +72,20 @@ func main() {
SetChangedFunc(func() {
app.Draw()
})
textView.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
textView.SetTextColor(tcell.ColorNames[config.Config.Colors.Text])
PrintHelp()
//textView.SetBorder(true)
textInput = tview.NewInputField()
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
})
textInput.SetDoneFunc(EnterCommand)
textInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyCtrlE {
app.SetFocus(treeView)
return nil
}
if event.Key() == tcell.KeyTab {
app.SetFocus(treeView)
return nil
}
if event.Key() == tcell.KeyDown {
offset, _ := textView.GetScrollOffset()
offset += 1
@@ -109,35 +114,338 @@ 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 {
fmt.Fprintln(textView, "[red]", err, "[-]")
}
}()
if err := sessionManager.StartManager(); err != nil {
PrintError(err)
}
LoadShortcuts()
app.Run()
}
// creates the TreeView for chats
func MakeTree() *tview.TreeView {
rootDir := "Chats"
chatRoot = tview.NewTreeNode(rootDir).
SetColor(tcell.ColorNames[config.Config.Colors.ListHeader])
treeView = tview.NewTreeView().
SetRoot(chatRoot).
SetCurrentNode(chatRoot)
treeView.SetBackgroundColor(tcell.ColorNames[config.Config.Colors.Background])
// 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.(messages.Chat)
SetDisplayedChat(recv)
} else {
// Collapse if visible, expand if collapsed.
node.SetExpanded(!node.IsExpanded())
}
})
return treeView
}
func handleFocusMessage(ev *tcell.EventKey) *tcell.EventKey {
if !textView.HasFocus() {
app.SetFocus(textView)
if curRegions != nil && len(curRegions) > 0 {
textView.Highlight(curRegions[len(curRegions)-1].Id)
}
}
return nil
}
func handleFocusInput(ev *tcell.EventKey) *tcell.EventKey {
ResetMsgSelection()
if !textInput.HasFocus() {
app.SetFocus(textInput)
}
return nil
}
func handleFocusContacts(ev *tcell.EventKey) *tcell.EventKey {
ResetMsgSelection()
if !treeView.HasFocus() {
app.SetFocus(treeView)
}
return nil
}
func handleSwitchPanels(ev *tcell.EventKey) *tcell.EventKey {
ResetMsgSelection()
if !textInput.HasFocus() {
app.SetFocus(textInput)
} else {
app.SetFocus(treeView)
}
return nil
}
func handleCommand(command string) func(ev *tcell.EventKey) *tcell.EventKey {
return func(ev *tcell.EventKey) *tcell.EventKey {
sessionManager.CommandChannel <- messages.Command{command, nil}
return nil
}
}
func handleCopyUser(ev *tcell.EventKey) *tcell.EventKey {
if hls := textView.GetHighlights(); len(hls) > 0 {
for _, val := range curRegions {
if val.Id == hls[0] {
clipboard.WriteAll(val.ContactId, "clipboard")
PrintText("copied id of " + val.ContactName + " to clipboard")
}
}
ResetMsgSelection()
} else if currentReceiver.Id != "" {
clipboard.WriteAll(currentReceiver.Id, "clipboard")
PrintText("copied id of " + currentReceiver.Name + " to clipboard")
}
return nil
}
func handlePasteUser(ev *tcell.EventKey) *tcell.EventKey {
if clip, err := clipboard.ReadAll("clipboard"); err == nil {
textInput.SetText(textInput.GetText() + " " + clip)
}
return nil
}
func handleQuit(ev *tcell.EventKey) *tcell.EventKey {
sessionManager.CommandChannel <- messages.Command{"disconnect", nil}
app.Stop()
return nil
}
func handleHelp(ev *tcell.EventKey) *tcell.EventKey {
PrintHelp()
return nil
}
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
}
}
func handleMessagesMove(amount int) func(ev *tcell.EventKey) *tcell.EventKey {
return func(ev *tcell.EventKey) *tcell.EventKey {
if curRegions == nil || len(curRegions) == 0 {
return nil
}
hls := textView.GetHighlights()
if len(hls) > 0 {
newId := GetOffsetMsgId(hls[0], amount)
if newId != "" {
textView.Highlight(newId)
}
} else {
if amount < 0 {
textView.Highlight(curRegions[0].Id)
} else {
textView.Highlight(curRegions[len(curRegions)-1].Id)
}
}
textView.ScrollToHighlight()
return nil
}
}
func handleChatPanelUp(ev *tcell.EventKey) *tcell.EventKey {
//TODO: scroll selection in treeView? or chatRoot? How?
return ev
}
func handleChatPanelDown(ev *tcell.EventKey) *tcell.EventKey {
return ev
}
func handleMessagesLast(ev *tcell.EventKey) *tcell.EventKey {
if curRegions == nil || len(curRegions) == 0 {
return nil
}
textView.Highlight(curRegions[len(curRegions)-1].Id)
textView.ScrollToHighlight()
return nil
}
func handleMessagesFirst(ev *tcell.EventKey) *tcell.EventKey {
if curRegions == nil || len(curRegions) == 0 {
return nil
}
textView.Highlight(curRegions[0].Id)
textView.ScrollToHighlight()
return nil
}
func handleExitMessages(ev *tcell.EventKey) *tcell.EventKey {
if curRegions == nil || len(curRegions) == 0 {
return nil
}
ResetMsgSelection()
app.SetFocus(textInput)
return nil
}
// load the key map
func LoadShortcuts() {
// global bindings for app
keyBindings = cbind.NewConfiguration()
if err := keyBindings.Set(config.Config.Keymap.FocusMessages, handleFocusMessage); err != nil {
PrintErrorMsg("focus_messages:", err)
}
if err := keyBindings.Set(config.Config.Keymap.FocusInput, handleFocusInput); err != nil {
PrintErrorMsg("focus_input:", err)
}
if err := keyBindings.Set(config.Config.Keymap.FocusChats, handleFocusContacts); err != nil {
PrintErrorMsg("focus_contacts:", err)
}
if err := keyBindings.Set(config.Config.Keymap.SwitchPanels, handleSwitchPanels); err != nil {
PrintErrorMsg("switch_panels:", err)
}
if err := keyBindings.Set(config.Config.Keymap.CommandRead, handleCommand("read")); err != nil {
PrintErrorMsg("command_read:", err)
}
if err := keyBindings.Set(config.Config.Keymap.Copyuser, handleCopyUser); err != nil {
PrintErrorMsg("copyuser:", err)
}
if err := keyBindings.Set(config.Config.Keymap.Pasteuser, handlePasteUser); err != nil {
PrintErrorMsg("pasteuser:", 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 := keyBindings.Set(config.Config.Keymap.CommandQuit, handleQuit); err != nil {
PrintErrorMsg("command_quit:", err)
}
if err := keyBindings.Set(config.Config.Keymap.CommandHelp, handleHelp); err != nil {
PrintErrorMsg("command_help:", err)
}
app.SetInputCapture(keyBindings.Capture)
// bindings for chat message text view
keysMessages := cbind.NewConfiguration()
if err := keysMessages.Set(config.Config.Keymap.MessageDownload, handleMessageCommand("download")); err != nil {
PrintErrorMsg("message_download:", err)
}
if err := keysMessages.Set(config.Config.Keymap.MessageOpen, handleMessageCommand("open")); err != nil {
PrintErrorMsg("message_open:", err)
}
if err := keysMessages.Set(config.Config.Keymap.Copyuser, handleCopyUser); err != nil {
PrintErrorMsg("copyuser:", err)
}
if err := keysMessages.Set(config.Config.Keymap.Pasteuser, handlePasteUser); err != nil {
PrintErrorMsg("pasteuser:", err)
}
if err := keysMessages.Set(config.Config.Keymap.MessageShow, handleMessageCommand("show")); err != nil {
PrintErrorMsg("message_show:", err)
}
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, handleMessagesMove(-1))
keysMessages.SetKey(tcell.ModNone, tcell.KeyDown, handleMessagesMove(1))
keysMessages.SetKey(tcell.ModNone, tcell.KeyPgUp, handleMessagesMove(-10))
keysMessages.SetKey(tcell.ModNone, tcell.KeyPgDn, handleMessagesMove(10))
keysMessages.SetRune(tcell.ModNone, 'k', handleMessagesMove(-1))
keysMessages.SetRune(tcell.ModNone, 'j', handleMessagesMove(1))
keysMessages.SetRune(tcell.ModNone, 'g', handleMessagesFirst)
keysMessages.SetRune(tcell.ModNone, 'G', handleMessagesLast)
keysMessages.SetRune(tcell.ModCtrl, 'u', handleMessagesMove(-10))
keysMessages.SetRune(tcell.ModCtrl, 'd', handleMessagesMove(10))
textView.SetInputCapture(keysMessages.Capture)
keysChatPanel := cbind.NewConfiguration()
keysChatPanel.SetRune(tcell.ModCtrl, 'u', handleChatPanelUp)
keysChatPanel.SetRune(tcell.ModCtrl, 'd', handleChatPanelDown)
treeView.SetInputCapture(keysChatPanel.Capture)
}
// prints help to chat view
func PrintHelp() {
fmt.Fprintln(textView, "[::b]WhatsCLI "+VERSION+"\n\n[-]")
fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
fmt.Fprintln(textView, "/name NewName = name selected contact")
fmt.Fprintln(textView, "/addname 1234567 NewName = add name for number")
fmt.Fprintln(textView, "/connect = (re)connect in case the connection dropped")
fmt.Fprintln(textView, "/load = reload contacts")
fmt.Fprintln(textView, "/quit = exit app")
fmt.Fprintln(textView, "/help = show this help\n")
cmdPrefix := config.Config.General.CmdPrefix
fmt.Fprintln(textView, "[-::u]Keys:[-::-]")
fmt.Fprintln(textView, "<Tab> = switch input/contacts")
fmt.Fprintln(textView, "<Up/Dn> = scroll history\n")
fmt.Fprintln(textView, "")
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, "[::b]", config.Config.Keymap.CommandQuit, "[::-] = Exit app")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::-]Message panel[-::-]")
fmt.Fprintln(textView, "[::b] Up/Down[::-] = select 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, "Config file in ->", config.GetConfigFilePath())
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "Type [::b]"+cmdPrefix+"commands[::-] to see all commands")
fmt.Fprintln(textView, "")
}
func PrintCommands() {
cmdPrefix := config.Config.General.CmdPrefix
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
fmt.Fprintln(textView, "")
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", config.Config.General.BacklogMsgQuantity, "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, "")
fmt.Fprintln(textView, "[-::-]Groups[-::-]")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"leave[::-] = Leave group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"create[::-] [user-id[] [user-id[] Group Subject = Create group with users")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"subject[::-] New Subject = Change subject of group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"add[::-] [user-id[] = Add user to group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"remove[::-] [user-id[] = Remove user from group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"admin[::-] [user-id[] = Set admin role for user in group")
fmt.Fprintln(textView, "[::b] "+cmdPrefix+"removeadmin[::-] [user-id[] = Remove admin role for user in group")
fmt.Fprintln(textView, "")
fmt.Fprintln(textView, "Use[::b]", config.Config.Keymap.Copyuser, "[::-]to copy a selected user id to clipboard")
fmt.Fprintln(textView, "Use[::b]", config.Config.Keymap.Pasteuser, "[::-]to paste clipboard to text input")
fmt.Fprintln(textView, "")
}
// called when text is entered by the user
@@ -149,280 +457,280 @@ func EnterCommand(key tcell.Key) {
textInput.SetText("")
return
}
if sndTxt == "/connect" {
//command
GetConnection()
textInput.SetText("")
return
}
if sndTxt == "/load" {
//command
LoadContacts()
textInput.SetText("")
return
}
if sndTxt == "/help" {
//command
cmdPrefix := config.Config.General.CmdPrefix
if sndTxt == cmdPrefix+"help" {
PrintHelp()
textInput.SetText("")
return
}
if sndTxt == "/quit" {
//command
if sndTxt == cmdPrefix+"commands" {
PrintCommands()
textInput.SetText("")
return
}
if sndTxt == cmdPrefix+"quit" {
sessionManager.CommandChannel <- messages.Command{"disconnect", nil}
app.Stop()
return
}
if strings.Index(sndTxt, "/addname ") == 0 {
//command
parts := strings.Split(sndTxt, " ")
if len(parts) < 3 {
fmt.Fprintln(textView, "Use /addname 1234567 NewName")
return
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:]
}
contact := whatsapp.Contact{
Jid: parts[1] + messages.CONTACTSUFFIX,
Name: strings.TrimPrefix(sndTxt, "/addname "+parts[1]+" "),
}
contactChannel <- contact
sessionManager.CommandChannel <- messages.Command{cmd, params}
textInput.SetText("")
return
}
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
if currentReceiver.Id == "" {
PrintText("no receiver")
textInput.SetText("")
return
}
// send message
msg := waMsg{
Wid: currentReceiver,
Text: sndTxt,
// no command, send as message
msg := messages.Command{
Name: "send",
Params: []string{currentReceiver.Id, sndTxt},
}
sendChannel <- msg
sessionManager.CommandChannel <- msg
textInput.SetText("")
}
// creates the TreeView for contacts
func MakeTree() *tview.TreeView {
rootDir := "Contacts"
contactRoot = tview.NewTreeNode(rootDir).
SetColor(tcell.ColorYellow)
treeView = tview.NewTreeView().
SetRoot(contactRoot).
SetCurrentNode(contactRoot)
// If a contact was selected, open it.
treeView.SetChangedFunc(func(node *tview.TreeNode) {
reference := node.GetReference()
if reference == nil {
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)
} 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
}
return event
})
return treeView
}
// 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(tcell.ColorGreen)
} else {
node.SetColor(tcell.ColorBlue)
}
contactRoot.AddChild(node)
if element == currentReceiver {
treeView.SetCurrentNode(node)
}
// get the next message id to select (highlighted + offset)
func GetOffsetMsgId(curId string, offset int) string {
if curRegions == nil || len(curRegions) == 0 {
return ""
}
}
// sets the current contact, loads text from storage to TextView
func SetDisplayedContact(wid string) {
currentReceiver = wid
textView.Clear()
textView.SetTitle(messages.GetIdName(wid))
textView.SetText(msgStore.GetMessagesString(wid))
}
// starts the receiver and message handling thread
func StartTextReceiver() error {
var wac = GetConnection()
err := LoginWithConnection(wac)
if err != nil {
return fmt.Errorf("%v\n", err)
}
handler = textHandler{}
wac.AddHandler(handler)
sendChannel = make(chan waMsg)
textChannel = make(chan whatsapp.TextMessage)
contactChannel = make(chan whatsapp.Contact)
for {
select {
case msg := <-sendChannel:
SendText(msg.Wid, msg.Text)
case rcvd := <-textChannel:
if msgStore.AddTextMessage(rcvd) {
app.QueueUpdateDraw(LoadContacts)
for idx, val := range curRegions {
if val.Id == curId {
arrPos := idx + offset
if len(curRegions) > arrPos && arrPos >= 0 {
return curRegions[arrPos].Id
}
case contact := <-contactChannel:
messages.SetIdName(contact.Jid, contact.Name)
app.QueueUpdateDraw(LoadContacts)
}
}
fmt.Fprintln(textView, "closing the receiver")
wac.Disconnect()
return nil
}
// 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 := GetConnection().Send(msg)
if err != nil {
fmt.Fprintln(textView, "[red]error sending message: ", err, "[-]")
if offset > 0 {
return curRegions[0].Id
} else {
msgStore.AddTextMessage(msg)
PrintTextMessage(msg)
return curRegions[len(curRegions)-1].Id
}
}
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[-]")
//}
// resets the selection in the textView and scrolls it down
func ResetMsgSelection() {
if len(textView.GetHighlights()) > 0 {
textView.Highlight("")
}
textView.ScrollToEnd()
}
// prints a text message to the TextView
func PrintTextMessage(msg whatsapp.TextMessage) {
fmt.Fprintln(textView, messages.GetTextMessageString(&msg))
// prints text to the TextView
func PrintText(txt string) {
fmt.Fprintln(textView, txt)
}
// handler struct for whatsapp callbacks
type textHandler struct{}
// HandleError implements the error handler interface for go-whatsapp
func (t textHandler) HandleError(err error) {
// TODO : handle go routine here
fmt.Fprintln(textView, "[red]error in textHandler : ", 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)
// prints an error to the TextView
func PrintError(err error) {
if err == nil {
return
}
PrintTextMessage(msg)
fmt.Fprintln(textView, "["+config.Config.Colors.Negative+"]", err.Error(), "[-]")
}
// 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,
},
Text: "[IMAGE] " + message.Caption,
// prints an error to the TextView
func PrintErrorMsg(text string, err error) {
if err == nil {
return
}
t.HandleTextMessage(msg)
fmt.Fprintln(textView, "["+config.Config.Colors.Negative+"]", text, err.Error(), "[-]")
}
func (t textHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[DOCUMENT] " + message.Title,
// prints an image attachment to the TextView (by message id)
func PrintImage(path string) {
var err error
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])
}
t.HandleTextMessage(msg)
}
func (t textHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
msg := whatsapp.TextMessage{
Info: whatsapp.MessageInfo{
RemoteJid: message.Info.RemoteJid,
SenderJid: message.Info.SenderJid,
FromMe: message.Info.FromMe,
Timestamp: message.Info.Timestamp,
},
Text: "[VIDEO] " + message.Caption,
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
}
}
t.HandleTextMessage(msg)
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,
},
Text: "[AUDIO]",
// updates the status bar
func UpdateStatusBar(statusInfo messages.SessionStatus) {
out := " "
if statusInfo.Connected {
out += "[" + config.Config.Colors.Positive + "]online[-]"
} else {
out += "[" + config.Config.Colors.Negative + "]offline[-]"
}
t.HandleTextMessage(msg)
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
infoBar.SetText(out)
//infoBar.SetText("🔋: ??%")
}
// add contact info to database TODO: when are these sent??
func (t textHandler) HandleNewContact(contact whatsapp.Contact) {
// redundant, wac has contacts
//contactChannel <- contact
// 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(wid.Name)
sessionManager.CommandChannel <- messages.Command{"select", []string{currentReceiver.Id}}
}
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
// app.QueueUpdate(func() {
// infoBar.SetText("🔋: " + string(msg.Percentage) + "%")
// })
//}
// get a string representation of all messages for chat
func getMessagesString(msgs []messages.Message) string {
out := ""
for _, msg := range msgs {
out += getTextMessageString(&msg)
out += "\n"
}
return out
}
// create a formatted string with regions based on message ID from a text message
//TODO: optimize, use Sprintf etc
func getTextMessageString(msg *messages.Message) string {
colorMe := config.Config.Colors.ChatMe
colorContact := config.Config.Colors.ChatContact
out := ""
text := tview.Escape(msg.Text)
if msg.Forwarded {
text = "[" + config.Config.Colors.ForwardedText + "]" + text + "[-]"
}
tim := time.Unix(int64(msg.Timestamp), 0)
time := tim.Format("02-01-06 15:04:05")
out += "[\""
out += msg.Id
out += "\"]"
if msg.FromMe { //msg from me
out += "[-::d](" + time + ") [" + colorMe + "::b]Me: [-::-]" + text
} else { // message from others
out += "[-::d](" + time + ") [" + colorContact + "::b]" + msg.ContactShort + ": [-::-]" + text
}
out += "[\"\"]"
return out
}
type UiHandler struct{}
func (u UiHandler) NewMessage(msg 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))
})
}
func (u UiHandler) NewScreen(msgs []messages.Message) {
go app.QueueUpdateDraw(func() {
textView.Clear()
screen := getMessagesString(msgs)
textView.SetText(screen)
curRegions = msgs
if screen == "" {
if currentReceiver.Id == "" {
PrintHelp()
} else {
PrintText("[::d] ~~~ no messages, press " + config.Config.Keymap.CommandBacklog + " to load backlog if available ~~~[::-]")
}
}
})
}
// loads the chat data from storage to the TreeView
func (u UiHandler) SetChats(ids []messages.Chat) {
go app.QueueUpdateDraw(func() {
chatRoot.ClearChildren()
oldId := currentReceiver.Id
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])
}
// store new currentReceiver, else the selection on the left goes off
if element.Id == oldId {
currentReceiver = element
}
chatRoot.AddChild(node)
if element.Id == currentReceiver.Id {
treeView.SetCurrentNode(node)
}
}
})
}
func (u UiHandler) PrintError(err error) {
PrintError(err)
}
func (u UiHandler) PrintText(msg string) {
PrintText(msg)
}
func (u UiHandler) PrintFile(path string) {
go app.QueueUpdateDraw(func() {
PrintImage(path)
})
}
func (u UiHandler) OpenFile(path string) {
open.Run(path)
}
func (u UiHandler) SetStatus(status messages.SessionStatus) {
go app.QueueUpdateDraw(func() {
UpdateStatusBar(status)
})
}
func (u UiHandler) GetWriter() io.Writer {
return textView
}

View File

@@ -1,89 +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
func SetConnection(conn *whatsapp.Conn) {
connection = conn
}
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
}
}
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
}
func SetIdName(id string, name string) {
contacts[id] = name
SaveContacts()
}
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)
}
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)
}
func GetHomeDir() string {
usr, err := user.Current()
if err != nil {
}
return usr.HomeDir + string(os.PathSeparator)
}

78
messages/messages.go Normal file
View 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"

1007
messages/session_manager.go Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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