Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cc68c9203 | ||
|
|
c3a6f98b13 | ||
|
|
21fec5a5d0 | ||
|
|
b2b798c514 | ||
|
|
5d63cf41eb | ||
|
|
e519c4a892 | ||
|
|
754106de62 | ||
|
|
07474c5b3d | ||
|
|
b142aef93c | ||
|
|
babe81c1aa | ||
|
|
b89ecfec79 | ||
|
|
5e7659b845 | ||
|
|
eee49d713d | ||
|
|
2a6b0efe68 | ||
|
|
9ee28a011c | ||
|
|
16a6519ba7 | ||
|
|
bb00a9a8f0 | ||
|
|
4ef8b76a55 | ||
|
|
6a832cbcf3 | ||
|
|
c2f3a012dc | ||
|
|
147feb90ec | ||
|
|
b39d4891e0 | ||
|
|
19455b0baa | ||
|
|
a37e6c7d3b | ||
|
|
82d2a6637d | ||
|
|
1eb3af823e | ||
|
|
773f9b783d | ||
|
|
e290a79064 | ||
|
|
7ae3967fbc | ||
|
|
5045ac59f5 | ||
|
|
2db7e51327 | ||
|
|
e698b66819 | ||
|
|
7fdafb477a | ||
|
|
13632156a1 | ||
|
|
08b69673e3 | ||
|
|
36b8bdac2f | ||
|
|
43c5795cc7 | ||
|
|
2063aeec91 | ||
|
|
310e9fa2ab | ||
|
|
c860542052 | ||
|
|
172fad115b | ||
|
|
cfd41b2de6 | ||
|
|
6eb872ef8a | ||
|
|
3f5ec9ff8c | ||
|
|
48fca0e6ad | ||
|
|
28d06730c7 | ||
|
|
fff039e776 | ||
|
|
82f6795aef | ||
|
|
4486b976ad | ||
|
|
bf8f8e77db |
22
Makefile
Normal file
22
Makefile
Normal file
@@ -0,0 +1,22 @@
|
||||
# Simple Makefile for go
|
||||
|
||||
build:
|
||||
go build
|
||||
|
||||
clean:
|
||||
go clean
|
||||
|
||||
run:
|
||||
go run .
|
||||
|
||||
install:
|
||||
go install .
|
||||
|
||||
get:
|
||||
go get
|
||||
|
||||
update:
|
||||
go get -u
|
||||
|
||||
release:
|
||||
./release.sh
|
||||
79
README.md
79
README.md
@@ -1,66 +1,57 @@
|
||||
# whatscli
|
||||
|
||||
A command line interface for whatsapp, based on go-whatsapp and tview
|
||||
A command line interface for whatsapp, based on [go-whatsapp](https://github.com/Rhymen/go-whatsapp) and [tview](https://github.com/rivo/tview)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ WhatsCLI v0.1.0 Help: /name = name contact | /quit = exit app | /load = reload contacts | <Tab> =│
|
||||
├──────────────────────────────┬─────────────────────────────────────────────────────────────────────┤
|
||||
│Contacts │ │
|
||||
│├──Normen │(03-22-13 21:18:24) Daniel: Sachste bescheid wenn der kram vorbei ist│
|
||||
│├──Lilou │? :) │
|
||||
│├──Boris │(03-22-13 21:37:29) Ich: Jo │
|
||||
│├──Malte │(03-22-13 22:02:13) Daniel: Mensch das geht ja lang.. │
|
||||
│├──Daniel │(03-22-13 22:02:53) Ich: Nu is schulz │
|
||||
│├──Seb │(03-22-13 22:03:08) Daniel: Jo bis gleich ! │
|
||||
│├──Bettina │(04-07-14 18:06:15) Daniel: Hey wie schauts :) │
|
||||
│├──Elternbeirat │(04-07-14 18:07:56) Ich: Ich komme laut Navigationssystem um 19:40 │
|
||||
│├──4911758758720-1565273421@g.│Uhr an │
|
||||
│├──491111250015@s.whatsapp.net│(04-07-14 18:08:33) Daniel: Sauber, ruf ma so ca 10 min vorher durch │
|
||||
│├──4911758758714-1537904683@g.│dann bin ich da :) │
|
||||
│├──491114171906-1448397341@g.u│(04-07-14 19:24:21) Ich: Bin jetzt in Bremen. Circa 10 Minuten │
|
||||
│├──491192855547-1561396191@g.u│(04-07-14 19:24:45) Daniel: Ok mach mich los │
|
||||
│├──491152456088@s.whatsapp.net│(07-27-14 19:14:19) Ich: Moin do. Sag' mal bist Du morgen um fünf in │
|
||||
│├──491107382606-1364411990@g.u│Bremen? Ich bräuchte jemanden um das Mischpult etc. wieder in einen │
|
||||
│├──491111250017-1603197565@g.u│LKW zu laden.. │
|
||||
│├──491131942996@s.whatsapp.net│(07-27-14 19:15:22) Daniel: Ich bin unterwegs, sorry │
|
||||
│├──491122978981@s.whatsapp.net│(07-27-14 19:15:52) Ich: Kein Ding, danke! │
|
||||
│├──491192855528@s.whatsapp.net│(07-27-14 19:24:50) Daniel: Jou bin quasi noch im breminale stress :)│
|
||||
│├──491154447429@s.whatsapp.net│(07-27-14 19:25:12) Ich: Na dann noch viel Spass :) │
|
||||
│├──491132457405-1526385826@g.u│(07-27-14 19:25:34) Daniel: Ja danke, bin froh wenns vorbei is ;) │
|
||||
│├──491103663035@s.whatsapp.net│(07-27-14 19:26:12) Ich: Augen zu und durch, Lock'n'Loll │
|
||||
│├──491113075747@s.whatsapp.net│(11-15-20 15:27:06) Ich: testjj │
|
||||
│├──491147048885@s.whatsapp.net├─────────────────────────────────────────────────────────────────────┤
|
||||
│├──491124146101@s.whatsapp.net│ │
|
||||
└──────────────────────────────┴─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
Things that work.
|
||||
|
||||
- Allows sending and receiving WhatsApp messages in a CLI interface
|
||||
- Connects through Web App API without browser
|
||||
- Connects through the Web App API without a browser
|
||||
- Allows sending and receiving WhatsApp messages in a command line app
|
||||
- Allows downloading and opening image/video/audio/document attachments
|
||||
- Uses QR code for simple setup
|
||||
- Binaries for Windows, Mac, Linux and RaspBerry Pi
|
||||
|
||||
### Caveats
|
||||
|
||||
This is a WIP. Heres some things you might expect to work that don't. Plus some other things I should mention.
|
||||
This is a WIP and mainly meant for my personal use. Heres some things you might expect to work that don't. Plus some other things I should mention.
|
||||
|
||||
- Only lists contacts and groups that have been messaged on phone
|
||||
- Only fetches a few messages for last contacted
|
||||
- To display names they have to be entered through the `/name` or `/addname` commands for each contact
|
||||
- No support for images, videos, documents etc
|
||||
- Only shows existing chats
|
||||
- Only fetches a few old messages
|
||||
- No incoming message notification / count
|
||||
- Not configurable at all
|
||||
- No proper connection drop handling
|
||||
- No uploading of images/video/audio/data
|
||||
- Not configurable at all (except through your terminal settings)
|
||||
- Leaves its config files in your home folder
|
||||
- FaceBook obviously doesn't endorse or like these kinds of apps and they're likely to break when they change stuff in their web app
|
||||
- FaceBook obviously doesn't endorse or like these kinds of apps and they're likely to break when FaceBook changes stuff in their web app
|
||||
|
||||
## Installation / Usage
|
||||
|
||||
How to get it running and use it
|
||||
How to get it running and how to use it
|
||||
|
||||
### Latest Release
|
||||
|
||||
Always fresh, always up to date.
|
||||
|
||||
- Download a release
|
||||
- Put the binary in your PATH (optional)
|
||||
- Run with `whatscli` (or double-click)
|
||||
- Scan QR code with WhatsApp on phone (maybe resize shell)
|
||||
- Scan the QR code with WhatsApp on your phone (maybe resize shell)
|
||||
|
||||
### Package Managers
|
||||
|
||||
Some unofficial 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)
|
||||
|
||||
- `git clone https://aur.archlinux.org/whatscli.git`
|
||||
- `cd whatscli`
|
||||
- `makepkg -si`
|
||||
- `yay -S whatscli`
|
||||
|
||||
|
||||
BIN
doc/screenshot.png
Normal file
BIN
doc/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 421 KiB |
19
go.mod
19
go.mod
@@ -3,11 +3,18 @@ module github.com/normen/whatscli
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a
|
||||
github.com/gdamore/tcell v1.4.0
|
||||
github.com/Rhymen/go-whatsapp v0.1.1
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
|
||||
github.com/mattn/go-colorable v0.1.1
|
||||
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743
|
||||
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
)
|
||||
|
||||
113
go.sum
113
go.sum
@@ -1,57 +1,128 @@
|
||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
|
||||
github.com/Rhymen/go-whatsapp v0.1.0 h1:XTXhFIQ/fx9jKObUnUX2Q+nh58EyeHNhX7DniE8xeuA=
|
||||
github.com/Rhymen/go-whatsapp v0.1.0/go.mod h1:xJSy+okeRjKkQEH/lEYrnekXB3PG33fqL0I6ncAkV50=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a h1:LW+rX0NY6LzMPa2hJcgmQlfiFJUihzOMAaIoCq+P3xc=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20201007125822-005103751b7a/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1 h1:OK+bCugQcr2YjyYKeDzULqCtM50TPUFM6LvQtszKfcw=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
|
||||
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
|
||||
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
|
||||
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743 h1:9BBjVJTRxuYBeCAv9DFH2hSzY0ujLx5sxMg5D3K/Xeg=
|
||||
github.com/rivo/tview v0.0.0-20201018122409-d551c850a743/go.mod h1:t7mcA3nlK9dxD1DMoz/DQRMWFMkGBUj6rJBM5VNfLFA=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893 h1:24As98PZlIdjZn6V4wUulAbYlG7RPg/du9A1FZdT/vs=
|
||||
github.com/rivo/tview v0.0.0-20201118063654-f007e9ad3893/go.mod h1:0ha5CGekam8ZV1kxkBxSlh7gfQ7YolUj2P/VruwH0QY=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7 h1:Z991aAXPjz0tLnj74pVXW3eWJ5lHMIBvbRfMq4M2jHA=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
479
main.go
479
main.go
@@ -1,13 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/normen/whatscli/messages"
|
||||
"github.com/rivo/tview"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type waMsg struct {
|
||||
@@ -15,26 +19,28 @@ type waMsg struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
var VERSION string = "v0.4.0"
|
||||
var VERSION string = "v0.6.8"
|
||||
|
||||
var sendChannel chan waMsg
|
||||
var textChannel chan whatsapp.TextMessage
|
||||
var otherChannel chan interface{}
|
||||
var contactChannel chan whatsapp.Contact
|
||||
|
||||
var sndTxt string = ""
|
||||
var currentReceiver string = ""
|
||||
var curRegions []string
|
||||
var textView *tview.TextView
|
||||
var treeView *tview.TreeView
|
||||
var textInput *tview.InputField
|
||||
var topBar *tview.TextView
|
||||
var connection *whatsapp.Conn
|
||||
|
||||
//var infoBar *tview.TextView
|
||||
var msgStore messages.MessageDatabase
|
||||
|
||||
var contactRoot *tview.TreeNode
|
||||
var handler textHandler
|
||||
var app *tview.Application
|
||||
|
||||
//var messages map[string]string
|
||||
|
||||
func main() {
|
||||
msgStore = messages.MessageDatabase{}
|
||||
msgStore.Init()
|
||||
@@ -44,32 +50,16 @@ func main() {
|
||||
gridLayout.SetRows(1, 0, 1)
|
||||
gridLayout.SetColumns(30, 0, 30)
|
||||
gridLayout.SetBorders(true)
|
||||
|
||||
//list := tview.NewList()
|
||||
////list.SetTitle("Contacts")
|
||||
////list.AddItem("List Contacts", "get the contacts", 'a', func() {
|
||||
//// list.Clear()
|
||||
//// var ids = msgStore.GetContactIds()
|
||||
//// for _, element := range ids {
|
||||
//// //fmt.Fprint(textView, "\n"+element)
|
||||
//// var elem = element
|
||||
//// list.AddItem(messages.GetIdName(element), "", '-', func() {
|
||||
//// currentReceiver = elem
|
||||
//// textView.Clear()
|
||||
//// textView.SetText(msgStore.GetMessagesString(elem))
|
||||
//// fmt.Fprint(textView, "\nNeuer Empfänger: ", elem)
|
||||
//// })
|
||||
//// }
|
||||
////})
|
||||
//list.ShowSecondaryText(false)
|
||||
//list.AddItem("Load", "Load Contacts", 'l', LoadContacts)
|
||||
//list.AddItem("Quit", "Press to exit", 'q', func() {
|
||||
// app.Stop()
|
||||
//})
|
||||
gridLayout.SetBackgroundColor(tcell.ColorBlack)
|
||||
|
||||
topBar = tview.NewTextView()
|
||||
topBar.SetDynamicColors(true)
|
||||
topBar.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Help: /name NewName | /addname 123456 NewName | /quit | <Tab> = contacts/message | <Up/Dn> = scroll")
|
||||
topBar.SetScrollable(false)
|
||||
topBar.SetText("[::b] WhatsCLI " + VERSION + " [-::d]Type /help for help")
|
||||
|
||||
//infoBar = tview.NewTextView()
|
||||
//infoBar.SetDynamicColors(true)
|
||||
//infoBar.SetText("🔋: ??%")
|
||||
|
||||
textView = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
@@ -79,7 +69,117 @@ func main() {
|
||||
app.Draw()
|
||||
})
|
||||
|
||||
fmt.Fprint(textView, "[::b]WhatsCLI "+VERSION+"\n\n[-][-::u]Commands:[-::-]\n/name NewName = name current contact\n/addname number NewName = name by number\n/load = reload contacts\n/quit = exit app\n\n[-::u]Keys:[-::-]\n<Tab> = switch input/contacts\n<Up/Dn> = scroll history")
|
||||
textView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlE {
|
||||
//TODO: Boilerplate
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlSpace {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return event
|
||||
}
|
||||
if event.Key() == tcell.KeyDown || event.Rune() == 'j' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], 1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[0])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyUp || event.Rune() == 'k' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
newId := GetOffsetMsgId(hls[0], -1)
|
||||
if newId != "" {
|
||||
textView.Highlight(newId)
|
||||
}
|
||||
} else {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'G' {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'g' {
|
||||
textView.Highlight(curRegions[0])
|
||||
textView.ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'd' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
DownloadMessageId(hls[0], false)
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'o' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
DownloadMessageId(hls[0], true)
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'i' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
fmt.Fprintln(textView, msgStore.GetMessageInfo(hls[0]))
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 's' {
|
||||
hls := textView.GetHighlights()
|
||||
if len(hls) > 0 {
|
||||
go PrintImage(hls[0])
|
||||
textView.Highlight("")
|
||||
textView.ScrollToEnd()
|
||||
app.SetFocus(textInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
// TODO: add better way
|
||||
messages.SetTextView(textView)
|
||||
PrintHelp()
|
||||
|
||||
//textView.SetBorder(true)
|
||||
|
||||
@@ -89,6 +189,17 @@ func main() {
|
||||
})
|
||||
textInput.SetDoneFunc(EnterCommand)
|
||||
textInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlE {
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlW {
|
||||
app.SetFocus(textView)
|
||||
if curRegions != nil && len(curRegions) > 0 {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
app.SetFocus(treeView)
|
||||
return nil
|
||||
@@ -121,6 +232,7 @@ 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(textView, 1, 1, 1, 3, 0, 0, false)
|
||||
gridLayout.AddItem(textInput, 2, 1, 1, 3, 0, 0, false)
|
||||
@@ -130,61 +242,13 @@ func main() {
|
||||
app.SetFocus(textInput)
|
||||
go func() {
|
||||
if err := StartTextReceiver(); err != nil {
|
||||
fmt.Fprint(textView, err)
|
||||
PrintError(err)
|
||||
}
|
||||
}()
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func EnterCommand(key tcell.Key) {
|
||||
if sndTxt == "" {
|
||||
return
|
||||
}
|
||||
if sndTxt == "/load" {
|
||||
//command
|
||||
LoadContacts()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/quit" {
|
||||
//command
|
||||
app.Stop()
|
||||
return
|
||||
}
|
||||
if strings.Index(sndTxt, "/addname ") == 0 {
|
||||
//command
|
||||
parts := strings.Split(sndTxt, " ")
|
||||
if len(parts) < 3 {
|
||||
fmt.Fprint(textView, "\nUse /addname 1234567 NewName")
|
||||
return
|
||||
}
|
||||
messages.SetIdName(parts[1]+messages.CONTACTSUFFIX, strings.TrimPrefix(sndTxt, "/addname "+parts[1]+" "))
|
||||
SetDisplayedContact(currentReceiver)
|
||||
LoadContacts()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if currentReceiver == "" {
|
||||
fmt.Fprint(textView, "\nNo recipient set")
|
||||
return
|
||||
}
|
||||
if strings.Index(sndTxt, "/name ") == 0 {
|
||||
//command
|
||||
messages.SetIdName(currentReceiver, strings.TrimPrefix(sndTxt, "/name "))
|
||||
SetDisplayedContact(currentReceiver)
|
||||
LoadContacts()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
// send message
|
||||
msg := waMsg{
|
||||
Wid: currentReceiver,
|
||||
Text: sndTxt,
|
||||
}
|
||||
sendChannel <- msg
|
||||
textInput.SetText("")
|
||||
}
|
||||
|
||||
// creates the TreeView for contacts
|
||||
func MakeTree() *tview.TreeView {
|
||||
rootDir := "Contacts"
|
||||
contactRoot = tview.NewTreeNode(rootDir).
|
||||
@@ -214,11 +278,193 @@ func MakeTree() *tview.TreeView {
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlSpace {
|
||||
app.SetFocus(textInput)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyCtrlW {
|
||||
app.SetFocus(textView)
|
||||
if curRegions != nil && len(curRegions) > 0 {
|
||||
textView.Highlight(curRegions[len(curRegions)-1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return treeView
|
||||
}
|
||||
|
||||
// prints help to chat view
|
||||
func PrintHelp() {
|
||||
fmt.Fprintln(textView, "[::b]WhatsCLI "+VERSION+"[-]")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::u]Keys:[-::-]")
|
||||
fmt.Fprintln(textView, "<Tab> = switch input/contacts")
|
||||
fmt.Fprintln(textView, "<Up/Dn> = scroll history/contacts")
|
||||
fmt.Fprintln(textView, "<Ctrl-W> = focus chat window")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::-]Chat window focused:[-::-]")
|
||||
fmt.Fprintln(textView, "<Up/Down> = select message")
|
||||
fmt.Fprintln(textView, "<d> = download attachment to $HOME/Downloads")
|
||||
fmt.Fprintln(textView, "<o> = download & open attachment")
|
||||
fmt.Fprintln(textView, "<s> = download & show image in chat using jp2a command")
|
||||
fmt.Fprintln(textView, "")
|
||||
fmt.Fprintln(textView, "[-::u]Commands:[-::-]")
|
||||
fmt.Fprintln(textView, "/connect = (re)connect in case the connection dropped")
|
||||
fmt.Fprintln(textView, "/quit = exit app")
|
||||
fmt.Fprintln(textView, "/help = show this help")
|
||||
fmt.Fprintln(textView, "")
|
||||
}
|
||||
|
||||
// called when text is entered by the user
|
||||
func EnterCommand(key tcell.Key) {
|
||||
if sndTxt == "" {
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEsc {
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/connect" {
|
||||
//command
|
||||
msgStore.Init()
|
||||
messages.Login()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/load" {
|
||||
//command
|
||||
LoadContacts()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/help" {
|
||||
//command
|
||||
PrintHelp()
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if sndTxt == "/quit" {
|
||||
//command
|
||||
app.Stop()
|
||||
return
|
||||
}
|
||||
if strings.Index(sndTxt, "/addname ") == 0 {
|
||||
//command
|
||||
parts := strings.Split(sndTxt, " ")
|
||||
if len(parts) < 3 {
|
||||
fmt.Fprintln(textView, "Use /addname 1234567 NewName")
|
||||
return
|
||||
}
|
||||
contact := whatsapp.Contact{
|
||||
Jid: parts[1] + messages.CONTACTSUFFIX,
|
||||
Name: strings.TrimPrefix(sndTxt, "/addname "+parts[1]+" "),
|
||||
}
|
||||
contactChannel <- contact
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
if currentReceiver == "" {
|
||||
fmt.Fprintln(textView, "[red]no contact selected[-]")
|
||||
return
|
||||
}
|
||||
if strings.Index(sndTxt, "/name ") == 0 {
|
||||
//command
|
||||
contact := whatsapp.Contact{
|
||||
Jid: currentReceiver,
|
||||
Name: strings.TrimPrefix(sndTxt, "/name "),
|
||||
}
|
||||
contactChannel <- contact
|
||||
textInput.SetText("")
|
||||
return
|
||||
}
|
||||
// send message
|
||||
msg := waMsg{
|
||||
Wid: currentReceiver,
|
||||
Text: sndTxt,
|
||||
}
|
||||
sendChannel <- msg
|
||||
textInput.SetText("")
|
||||
}
|
||||
|
||||
// get the next message id to select (highlighted + offset)
|
||||
func GetOffsetMsgId(curId string, offset int) string {
|
||||
if curRegions == nil || len(curRegions) == 0 {
|
||||
return ""
|
||||
}
|
||||
for idx, val := range curRegions {
|
||||
if val == curId {
|
||||
arrPos := idx + offset
|
||||
if len(curRegions) > arrPos && arrPos >= 0 {
|
||||
return curRegions[arrPos]
|
||||
}
|
||||
}
|
||||
}
|
||||
if offset > 0 {
|
||||
return curRegions[0]
|
||||
} else {
|
||||
return curRegions[len(curRegions)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// prints an error to the TextView
|
||||
func PrintError(err error) {
|
||||
fmt.Fprintln(textView, "[red]", err.Error(), "[-]")
|
||||
}
|
||||
|
||||
// prints an image attachment to the TextView (by message id)
|
||||
func PrintImage(id string) {
|
||||
var err error
|
||||
var path string
|
||||
PrintText("[::d]loading..[::-]")
|
||||
if path, err = msgStore.DownloadMessage(id, false); err == nil {
|
||||
cmd := exec.Command("jp2a", "--color", path)
|
||||
var stdout io.ReadCloser
|
||||
if stdout, err = cmd.StdoutPipe(); err == nil {
|
||||
if err = cmd.Start(); err == nil {
|
||||
reader := bufio.NewReader(stdout)
|
||||
io.Copy(tview.ANSIWriter(textView), reader)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
PrintError(err)
|
||||
}
|
||||
|
||||
// initiates a download of a specific message attachment in a new go routine
|
||||
func DownloadMessageId(id string, open bool) {
|
||||
PrintText("[::d]loading..[::-]")
|
||||
go func() {
|
||||
if result, err := msgStore.DownloadMessage(id, open); err == nil {
|
||||
fmt.Fprintln(textView, "[::d]Downloaded as [yellow]", result, "[-::-]")
|
||||
} else {
|
||||
PrintError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// notifies about a new message if its recent
|
||||
func NotifyMsg(msg whatsapp.TextMessage) {
|
||||
if int64(msg.Info.Timestamp) > time.Now().Unix()-30 {
|
||||
//fmt.Print("\a")
|
||||
//err := beeep.Notify(messages.GetIdName(msg.Info.RemoteJid), msg.Text, "")
|
||||
//if err != nil {
|
||||
// fmt.Fprintln(textView, "[red]error in notification[-]")
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// loads the contact data from storage to the TreeView
|
||||
func LoadContacts() {
|
||||
var ids = msgStore.GetContactIds()
|
||||
contactRoot.ClearChildren()
|
||||
@@ -238,17 +484,20 @@ func LoadContacts() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
msgTxt, regIds := msgStore.GetMessagesString(wid)
|
||||
textView.SetText(msgTxt)
|
||||
curRegions = regIds
|
||||
}
|
||||
|
||||
// StartTextReceiver starts the handler for the text messages received
|
||||
// starts the receiver and message handling thread
|
||||
func StartTextReceiver() error {
|
||||
var wac = GetConnection()
|
||||
err := LoginWithConnection(wac)
|
||||
var wac = messages.GetConnection()
|
||||
err := messages.LoginWithConnection(wac)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v\n", err)
|
||||
}
|
||||
@@ -256,21 +505,29 @@ func StartTextReceiver() error {
|
||||
wac.AddHandler(handler)
|
||||
sendChannel = make(chan waMsg)
|
||||
textChannel = make(chan whatsapp.TextMessage)
|
||||
otherChannel = make(chan interface{})
|
||||
contactChannel = make(chan whatsapp.Contact)
|
||||
for {
|
||||
select {
|
||||
case msg := <-sendChannel:
|
||||
SendText(msg.Wid, msg.Text)
|
||||
case rcvd := <-textChannel:
|
||||
if msgStore.AddTextMessage(rcvd) {
|
||||
if msgStore.AddTextMessage(&rcvd) {
|
||||
app.QueueUpdateDraw(LoadContacts)
|
||||
}
|
||||
case other := <-otherChannel:
|
||||
msgStore.AddOtherMessage(&other)
|
||||
case contact := <-contactChannel:
|
||||
messages.SetIdName(contact.Jid, contact.Name)
|
||||
app.QueueUpdateDraw(LoadContacts)
|
||||
}
|
||||
}
|
||||
fmt.Fprint(textView, "\n"+"closing the receiver")
|
||||
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{
|
||||
@@ -281,25 +538,22 @@ func SendText(wid string, text string) {
|
||||
Text: text,
|
||||
}
|
||||
|
||||
PrintTextMessage(msg)
|
||||
//TODO: workaround for error when receiving&sending
|
||||
connection.RemoveHandlers()
|
||||
_, err := connection.Send(msg)
|
||||
msgStore.AddTextMessage(msg)
|
||||
connection.AddHandler(handler)
|
||||
_, err := messages.GetConnection().Send(msg)
|
||||
if err != nil {
|
||||
fmt.Fprint(textView, "\nerror sending message: %v", err)
|
||||
PrintError(err)
|
||||
} else {
|
||||
//fmt.Fprint(textView, "\nSent msg with ID: %v", msgID)
|
||||
msgStore.AddTextMessage(&msg)
|
||||
PrintTextMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// handler struct for whatsapp callbacks
|
||||
type textHandler struct{}
|
||||
|
||||
// HandleError implements the handler interface for go-whatsapp
|
||||
// HandleError implements the error handler interface for go-whatsapp
|
||||
func (t textHandler) HandleError(err error) {
|
||||
// TODO : handle go routine here
|
||||
fmt.Fprint(textView, "\nerror in textHandler : %v", err)
|
||||
PrintError(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -307,16 +561,18 @@ func (t textHandler) HandleError(err error) {
|
||||
func (t textHandler) HandleTextMessage(msg whatsapp.TextMessage) {
|
||||
textChannel <- msg
|
||||
if msg.Info.RemoteJid != currentReceiver {
|
||||
//fmt.Print("\a")
|
||||
NotifyMsg(msg)
|
||||
return
|
||||
}
|
||||
PrintTextMessage(msg)
|
||||
// add to regions if current window, otherwise its not selectable
|
||||
id := msg.Info.Id
|
||||
app.QueueUpdate(func() {
|
||||
curRegions = append(curRegions, id)
|
||||
})
|
||||
}
|
||||
|
||||
func PrintTextMessage(msg whatsapp.TextMessage) {
|
||||
fmt.Fprint(textView, messages.GetTextMessageString(&msg))
|
||||
}
|
||||
|
||||
// methods to convert messages to TextMessage
|
||||
func (t textHandler) HandleImageMessage(message whatsapp.ImageMessage) {
|
||||
msg := whatsapp.TextMessage{
|
||||
Info: whatsapp.MessageInfo{
|
||||
@@ -324,10 +580,12 @@ func (t textHandler) HandleImageMessage(message whatsapp.ImageMessage) {
|
||||
SenderJid: message.Info.SenderJid,
|
||||
FromMe: message.Info.FromMe,
|
||||
Timestamp: message.Info.Timestamp,
|
||||
Id: message.Info.Id,
|
||||
},
|
||||
Text: "[IMAGE] " + message.Caption,
|
||||
}
|
||||
t.HandleTextMessage(msg)
|
||||
otherChannel <- message
|
||||
}
|
||||
|
||||
func (t textHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) {
|
||||
@@ -337,10 +595,12 @@ func (t textHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) {
|
||||
SenderJid: message.Info.SenderJid,
|
||||
FromMe: message.Info.FromMe,
|
||||
Timestamp: message.Info.Timestamp,
|
||||
Id: message.Info.Id,
|
||||
},
|
||||
Text: "[DOCUMENT] " + message.Title,
|
||||
}
|
||||
t.HandleTextMessage(msg)
|
||||
otherChannel <- message
|
||||
}
|
||||
|
||||
func (t textHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||
@@ -350,10 +610,12 @@ func (t textHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||
SenderJid: message.Info.SenderJid,
|
||||
FromMe: message.Info.FromMe,
|
||||
Timestamp: message.Info.Timestamp,
|
||||
Id: message.Info.Id,
|
||||
},
|
||||
Text: "[VIDEO] " + message.Caption,
|
||||
}
|
||||
t.HandleTextMessage(msg)
|
||||
otherChannel <- message
|
||||
}
|
||||
|
||||
func (t textHandler) HandleAudioMessage(message whatsapp.AudioMessage) {
|
||||
@@ -363,8 +625,23 @@ func (t textHandler) HandleAudioMessage(message whatsapp.AudioMessage) {
|
||||
SenderJid: message.Info.SenderJid,
|
||||
FromMe: message.Info.FromMe,
|
||||
Timestamp: message.Info.Timestamp,
|
||||
Id: message.Info.Id,
|
||||
},
|
||||
Text: "[AUDIO]",
|
||||
}
|
||||
t.HandleTextMessage(msg)
|
||||
otherChannel <- message
|
||||
}
|
||||
|
||||
// add contact info to database (not needed, internal db of connection is used)
|
||||
func (t textHandler) HandleNewContact(contact whatsapp.Contact) {
|
||||
// redundant, wac has contacts
|
||||
//contactChannel <- contact
|
||||
}
|
||||
|
||||
// handle battery messages
|
||||
//func (t textHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
|
||||
// app.QueueUpdate(func() {
|
||||
// infoBar.SetText("🔋: " + string(msg.Percentage) + "%")
|
||||
// })
|
||||
//}
|
||||
|
||||
@@ -5,10 +5,14 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
)
|
||||
|
||||
var contacts map[string]string
|
||||
var connection *whatsapp.Conn
|
||||
|
||||
// loads custom contacts from disk
|
||||
func LoadContacts() {
|
||||
contacts = make(map[string]string)
|
||||
file, err := os.Open(GetHomeDir() + ".whatscli.contacts")
|
||||
@@ -23,6 +27,7 @@ func LoadContacts() {
|
||||
}
|
||||
}
|
||||
|
||||
// saves custom contacts to disk
|
||||
func SaveContacts() {
|
||||
file, err := os.Create(GetHomeDir() + ".whatscli.contacts")
|
||||
if err != nil {
|
||||
@@ -37,18 +42,47 @@ func SaveContacts() {
|
||||
return
|
||||
}
|
||||
|
||||
// sets a new name for a whatsapp id
|
||||
func SetIdName(id string, name string) {
|
||||
contacts[id] = name
|
||||
SaveContacts()
|
||||
}
|
||||
|
||||
// gets a pretty name for a whatsapp id
|
||||
func GetIdName(id string) string {
|
||||
if _, ok := contacts[id]; ok {
|
||||
return contacts[id]
|
||||
}
|
||||
if val, ok := connection.Store.Contacts[id]; ok {
|
||||
if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Notify != "" {
|
||||
return val.Notify
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(id, CONTACTSUFFIX)
|
||||
}
|
||||
|
||||
// gets a short name for a whatsapp id
|
||||
func GetIdShort(id string) string {
|
||||
if val, ok := connection.Store.Contacts[id]; ok {
|
||||
if val.Short != "" {
|
||||
return val.Short
|
||||
} else if val.Name != "" {
|
||||
return val.Name
|
||||
} else if val.Notify != "" {
|
||||
return val.Notify
|
||||
}
|
||||
}
|
||||
if _, ok := contacts[id]; ok {
|
||||
return contacts[id]
|
||||
}
|
||||
return strings.TrimSuffix(id, CONTACTSUFFIX)
|
||||
}
|
||||
|
||||
// gets the OS home dir with a path separator at the end
|
||||
func GetHomeDir() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package main
|
||||
package messages
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/rivo/tview"
|
||||
"os"
|
||||
"os/user"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/normen/whatscli/qrcode"
|
||||
)
|
||||
|
||||
var textView *tview.TextView
|
||||
|
||||
// TODO: remove this circular dependeny in favor of a better way
|
||||
func SetTextView(tv *tview.TextView) {
|
||||
textView = tv
|
||||
}
|
||||
|
||||
// gets an existing connection or creates one
|
||||
func GetConnection() *whatsapp.Conn {
|
||||
var wac *whatsapp.Conn
|
||||
if connection == nil {
|
||||
@@ -37,8 +44,8 @@ func Login() error {
|
||||
// LoginWithConnection logs in the user using a provided connection. It ries to see if a session already exists. If not, tries to create a
|
||||
// new one using qr scanned on the terminal.
|
||||
func LoginWithConnection(wac *whatsapp.Conn) error {
|
||||
if wac.Info != nil && wac.Info.Connected {
|
||||
return nil
|
||||
if connection != nil && connection.GetConnected() {
|
||||
connection.Disconnect()
|
||||
}
|
||||
//load saved session
|
||||
session, err := readSession()
|
||||
@@ -68,7 +75,6 @@ func LoginWithConnection(wac *whatsapp.Conn) error {
|
||||
return fmt.Errorf("error saving session: %v\n", err)
|
||||
}
|
||||
//<-time.After(3 * time.Second)
|
||||
//fmt.Fprint(textView, "\nlogin successful")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,13 +83,7 @@ func Logout() error {
|
||||
return removeSession()
|
||||
}
|
||||
|
||||
func GetHomeDir() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
}
|
||||
return usr.HomeDir + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
// reads the session file from disk
|
||||
func readSession() (whatsapp.Session, error) {
|
||||
session := whatsapp.Session{}
|
||||
file, err := os.Open(GetHomeDir() + ".whatscli.session")
|
||||
@@ -99,6 +99,7 @@ func readSession() (whatsapp.Session, error) {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// saves the session file to disk
|
||||
func writeSession(session whatsapp.Session) error {
|
||||
file, err := os.Create(GetHomeDir() + ".whatscli.session")
|
||||
if err != nil {
|
||||
@@ -113,6 +114,7 @@ func writeSession(session whatsapp.Session) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletes the session file from disk
|
||||
func removeSession() error {
|
||||
return os.Remove(GetHomeDir() + ".whatscli.session")
|
||||
}
|
||||
@@ -1,33 +1,44 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/rivo/tview"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
const GROUPSUFFIX = "@g.us"
|
||||
const CONTACTSUFFIX = "@s.whatsapp.net"
|
||||
|
||||
type MessageDatabase struct {
|
||||
textMessages map[string][]whatsapp.TextMessage
|
||||
latestMessage map[string]uint64
|
||||
textMessages map[string][]*whatsapp.TextMessage // text messages stored by RemoteJid
|
||||
messagesById map[string]*whatsapp.TextMessage // text messages stored by message ID
|
||||
latestMessage map[string]uint64 // last message from RemoteJid
|
||||
otherMessages map[string]*interface{} // other non-text messages, stored by ID
|
||||
}
|
||||
|
||||
// initialize the database
|
||||
func (db *MessageDatabase) Init() {
|
||||
//var this = *db
|
||||
(*db).textMessages = make(map[string][]whatsapp.TextMessage)
|
||||
(*db).textMessages = make(map[string][]*whatsapp.TextMessage)
|
||||
(*db).messagesById = make(map[string]*whatsapp.TextMessage)
|
||||
(*db).otherMessages = make(map[string]*interface{})
|
||||
(*db).latestMessage = make(map[string]uint64)
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool {
|
||||
// add a text message to the database, stored by RemoteJid
|
||||
func (db *MessageDatabase) AddTextMessage(msg *whatsapp.TextMessage) bool {
|
||||
//var this = *db
|
||||
var didNew = false
|
||||
var wid = msg.Info.RemoteJid
|
||||
if (*db).textMessages[wid] == nil {
|
||||
var newArr = []whatsapp.TextMessage{}
|
||||
var newArr = []*whatsapp.TextMessage{}
|
||||
(*db).textMessages[wid] = newArr
|
||||
(*db).latestMessage[wid] = msg.Info.Timestamp
|
||||
didNew = true
|
||||
@@ -36,12 +47,33 @@ func (db *MessageDatabase) AddTextMessage(msg whatsapp.TextMessage) bool {
|
||||
didNew = true
|
||||
}
|
||||
(*db).textMessages[wid] = append((*db).textMessages[wid], msg)
|
||||
(*db).messagesById[msg.Info.Id] = msg
|
||||
sort.Slice((*db).textMessages[wid], func(i, j int) bool {
|
||||
return (*db).textMessages[wid][i].Info.Timestamp < (*db).textMessages[wid][j].Info.Timestamp
|
||||
})
|
||||
return didNew
|
||||
}
|
||||
|
||||
// add audio/video/image/doc message, stored by message id
|
||||
func (db *MessageDatabase) AddOtherMessage(msg *interface{}) {
|
||||
var id = ""
|
||||
switch v := (*msg).(type) {
|
||||
default:
|
||||
case whatsapp.ImageMessage:
|
||||
id = v.Info.Id
|
||||
case whatsapp.DocumentMessage:
|
||||
id = v.Info.Id
|
||||
case whatsapp.AudioMessage:
|
||||
id = v.Info.Id
|
||||
case whatsapp.VideoMessage:
|
||||
id = v.Info.Id
|
||||
}
|
||||
if id != "" {
|
||||
(*db).otherMessages[id] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// get an array of all chat ids
|
||||
func (db *MessageDatabase) GetContactIds() []string {
|
||||
//var this = *db
|
||||
keys := make([]string, len((*db).textMessages))
|
||||
@@ -53,32 +85,140 @@ func (db *MessageDatabase) GetContactIds() []string {
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return (*db).latestMessage[keys[i]] > (*db).latestMessage[keys[j]]
|
||||
})
|
||||
//sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func (db *MessageDatabase) GetMessagesString(wid string) string {
|
||||
//var this = *db
|
||||
var out = ""
|
||||
for _, element := range (*db).textMessages[wid] {
|
||||
out += GetTextMessageString(&element)
|
||||
func (db *MessageDatabase) GetMessageInfo(id string) string {
|
||||
if _, ok := (*db).otherMessages[id]; ok {
|
||||
return "[yellow]OtherMessage[-]"
|
||||
}
|
||||
out := ""
|
||||
if msg, ok := (*db).messagesById[id]; ok {
|
||||
out += "[yellow]ID: " + msg.Info.Id + "[-]\n"
|
||||
out += "[yellow]PushName: " + msg.Info.PushName + "[-]\n"
|
||||
out += "[yellow]RemoteJid: " + msg.Info.RemoteJid + "[-]\n"
|
||||
out += "[yellow]SenderJid: " + msg.Info.SenderJid + "[-]\n"
|
||||
out += "[yellow]Participant: " + msg.ContextInfo.Participant + "[-]\n"
|
||||
out += "[yellow]QuotedMessageID: " + msg.ContextInfo.QuotedMessageID + "[-]\n"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// get a string containing all messages for a chat by chat id
|
||||
func (db *MessageDatabase) GetMessagesString(wid string) (string, []string) {
|
||||
//var this = *db
|
||||
var out = ""
|
||||
var arr = []string{}
|
||||
for _, element := range (*db).textMessages[wid] {
|
||||
out += GetTextMessageString(element)
|
||||
out += "\n"
|
||||
arr = append(arr, element.Info.Id)
|
||||
}
|
||||
return out, arr
|
||||
}
|
||||
|
||||
// create a formatted string with regions based on message ID from a text message
|
||||
func GetTextMessageString(msg *whatsapp.TextMessage) string {
|
||||
out := ""
|
||||
text := tview.Escape((*msg).Text)
|
||||
tim := time.Unix(int64((*msg).Info.Timestamp), 0)
|
||||
out += "[\""
|
||||
out += (*msg).Info.Id
|
||||
out += "\"]"
|
||||
if (*msg).Info.FromMe { //msg from me
|
||||
out += "\n[-:-:d](" + tim.Format("01-02-06 15:04:05") + ") [blue:-:b]Me: [-:-:-]" + text
|
||||
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 += "\n[-:-:d](" + tim.Format("01-02-06 15:04:05") + ") [green:-:b]" + GetIdName(userId) + ": [-:-:-]" + text
|
||||
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort(userId) + ": [-::-]" + text
|
||||
} else { // message from others
|
||||
out += "\n[-:-:d](" + tim.Format("01-02-06 15:04:05") + ") [green:-:b]" + GetIdName((*msg).Info.RemoteJid) + ": [-:-:-]" + text
|
||||
out += "[-::d](" + tim.Format("02-01-06 15:04:05") + ") [green::b]" + GetIdShort((*msg).Info.RemoteJid) + ": [-::-]" + text
|
||||
}
|
||||
out += "[\"\"]"
|
||||
return out
|
||||
}
|
||||
|
||||
// load data for message specified by message id TODO: support types
|
||||
func (db *MessageDatabase) LoadMessageData(wid string) ([]byte, error) {
|
||||
if msg, ok := (*db).otherMessages[wid]; ok {
|
||||
switch v := (*msg).(type) {
|
||||
default:
|
||||
case whatsapp.ImageMessage:
|
||||
return v.Download()
|
||||
case whatsapp.DocumentMessage:
|
||||
//return v.Download()
|
||||
case whatsapp.AudioMessage:
|
||||
//return v.Download()
|
||||
case whatsapp.VideoMessage:
|
||||
//return v.Download()
|
||||
}
|
||||
}
|
||||
return []byte{}, errors.New("This is not an image message")
|
||||
}
|
||||
|
||||
// attempts to download a messages attachments, returns path or error message
|
||||
func (db *MessageDatabase) DownloadMessage(wid string, open bool) (string, error) {
|
||||
if msg, ok := (*db).otherMessages[wid]; ok {
|
||||
var fileName string = GetHomeDir() + "Downloads" + string(os.PathSeparator)
|
||||
switch v := (*msg).(type) {
|
||||
default:
|
||||
case whatsapp.ImageMessage:
|
||||
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "image/")
|
||||
if _, err := os.Stat(fileName); err == nil {
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
}
|
||||
case whatsapp.DocumentMessage:
|
||||
fileName += v.Info.Id + "." + strings.TrimPrefix(strings.TrimPrefix(v.Type, "application/"), "document/")
|
||||
if _, err := os.Stat(fileName); err == nil {
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
}
|
||||
case whatsapp.AudioMessage:
|
||||
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "audio/")
|
||||
if _, err := os.Stat(fileName); err == nil {
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
}
|
||||
case whatsapp.VideoMessage:
|
||||
fileName += v.Info.Id + "." + strings.TrimPrefix(v.Type, "video/")
|
||||
if _, err := os.Stat(fileName); err == nil {
|
||||
return fileName, err
|
||||
} else if os.IsNotExist(err) {
|
||||
if data, err := v.Download(); err == nil {
|
||||
return saveAttachment(data, fileName, open)
|
||||
} else {
|
||||
return fileName, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("No attachments found")
|
||||
}
|
||||
|
||||
// helper to save an attachment and open it if specified
|
||||
func saveAttachment(data []byte, path string, openIt bool) (string, error) {
|
||||
err := ioutil.WriteFile(path, data, 0644)
|
||||
if err == nil {
|
||||
if openIt {
|
||||
open.Run(path)
|
||||
}
|
||||
} else {
|
||||
return path, err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ var (
|
||||
type QRCodeString string
|
||||
|
||||
func (v *QRCodeString) Print() {
|
||||
fmt.Fprint(outer, *v)
|
||||
fmt.Fprintln(outer, *v)
|
||||
}
|
||||
|
||||
type qrcodeTerminal struct {
|
||||
|
||||
49
release.sh
Executable file
49
release.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# get verison from main.go VERSION string
|
||||
if [ $# -eq 0 ]; then
|
||||
VERSION=$(cat main.go|grep "VERSION string"| awk -v FS="(\")" '{print $2}')
|
||||
else
|
||||
VERSION=$1
|
||||
fi
|
||||
echo Releasing $VERSION
|
||||
WINF=whatscli-$VERSION-windows.zip
|
||||
LINUXF=whatscli-$VERSION-linux.zip
|
||||
MACF=whatscli-$VERSION-macos.zip
|
||||
RASPIF=whatscli-$VERSION-raspberrypi.zip
|
||||
|
||||
# build zip files with binaries
|
||||
GOOS=darwin go build -o whatscli
|
||||
zip $MACF whatscli
|
||||
rm whatscli
|
||||
GOOS=windows go build -o whatscli.exe
|
||||
zip $WINF whatscli.exe
|
||||
rm whatscli.exe
|
||||
GOOS=linux go build -o whatscli
|
||||
zip $LINUXF whatscli
|
||||
rm whatscli
|
||||
GOOS=linux GOARCH=arm GOARM=5 go build -o whatscli
|
||||
zip $RASPIF whatscli
|
||||
rm whatscli
|
||||
|
||||
# publish to github
|
||||
git pull
|
||||
LASTTAG=$(git describe --tags --abbrev=0)
|
||||
git log $LASTTAG..HEAD --no-decorate --pretty=format:"- %s" --abbrev-commit > changes.txt
|
||||
vim changes.txt
|
||||
gh release create $VERSION $LINUXF $MACF $WINF $RASPIF -F changes.txt -t $VERSION
|
||||
rm changes.txt
|
||||
rm *.zip
|
||||
|
||||
# update homebrew tap
|
||||
URL="https://github.com/normen/whatscli/archive/$VERSION.tar.gz"
|
||||
wget $URL
|
||||
SHASUM=$(shasum -a 256 $VERSION.tar.gz|awk '{print$1}')
|
||||
rm $VERSION.tar.gz
|
||||
cd ../../BrewCode/homebrew-tap
|
||||
sed -i bak "s/sha256 \".*/sha256 \"$SHASUM\"/" Formula/whatscli.rb
|
||||
sed -i bak "s!url \".*!url \"$URL\"!" Formula/whatscli.rb
|
||||
rm Formula/whatscli.rbbak
|
||||
git add -A
|
||||
git commit -m "update whatscli to $VERSION"
|
||||
git push
|
||||
Reference in New Issue
Block a user