forked from jshiffer/matterbridge
Update whatsapp vendor and fix a panic (#1209)
* Fix another whatsapp panic * Update whatsapp vendor
This commit is contained in:
parent
e8167ee3d7
commit
2f506425c2
@ -78,7 +78,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
|
||||
senderJID := message.Info.SenderJid
|
||||
if len(senderJID) == 0 {
|
||||
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
|
||||
if message.Info.Source != nil {
|
||||
if message.Info.Source != nil && message.Info.Source.Participant != nil {
|
||||
senderJID = *message.Info.Source.Participant
|
||||
}
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ require (
|
||||
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
|
||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819
|
||||
github.com/d5/tengo/v2 v2.6.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
|
4
go.sum
4
go.sum
@ -43,8 +43,8 @@ github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0 h1:TO7d4rocnNFng6ZQrPe7U6WqHtK5eHEMrgrnnM/72IQ=
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
|
||||
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334 h1:kb1zvD+xd+XbPUdQ0lMxnRaQ76N5C9vMAClLi8Dyw1Y=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819 h1:LthbEFUDcL9ZSRIs9m9JjThBSKrW6aIj8YGIT7G/SSk=
|
||||
github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819/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=
|
||||
|
4
vendor/github.com/Rhymen/go-whatsapp/README.md
generated
vendored
4
vendor/github.com/Rhymen/go-whatsapp/README.md
generated
vendored
@ -70,6 +70,10 @@ func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) {
|
||||
fmt.Println(message)
|
||||
}
|
||||
|
||||
func (myHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
|
||||
fmt.Println(message)
|
||||
}
|
||||
|
||||
wac.AddHandler(myHandler{})
|
||||
```
|
||||
The message handlers are all optional, you don't need to implement anything but the error handler to implement the interface. The ImageMessage, VideoMessage, AudioMessage and DocumentMessage provide a Download function to get the media data.
|
||||
|
14
vendor/github.com/Rhymen/go-whatsapp/conn.go
generated
vendored
14
vendor/github.com/Rhymen/go-whatsapp/conn.go
generated
vendored
@ -88,6 +88,8 @@ type Conn struct {
|
||||
Store *Store
|
||||
ServerLastSeen time.Time
|
||||
|
||||
timeTag string // last 3 digits obtained after a successful login takeover
|
||||
|
||||
longClientName string
|
||||
shortClientName string
|
||||
clientVersion string
|
||||
@ -156,8 +158,8 @@ func (wac *Conn) connect() (err error) {
|
||||
}()
|
||||
|
||||
dialer := &websocket.Dialer{
|
||||
ReadBufferSize: 25 * 1024 * 1024,
|
||||
WriteBufferSize: 10 * 1024 * 1024,
|
||||
ReadBufferSize: 0,
|
||||
WriteBufferSize: 0,
|
||||
HandshakeTimeout: wac.msgTimeout,
|
||||
Proxy: wac.Proxy,
|
||||
}
|
||||
@ -246,3 +248,11 @@ func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wac *Conn) GetConnected() bool {
|
||||
return wac.connected
|
||||
}
|
||||
|
||||
func (wac *Conn) GetLoggedIn() bool {
|
||||
return wac.loggedIn
|
||||
}
|
||||
|
2
vendor/github.com/Rhymen/go-whatsapp/errors.go
generated
vendored
2
vendor/github.com/Rhymen/go-whatsapp/errors.go
generated
vendored
@ -2,6 +2,7 @@ package whatsapp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -20,6 +21,7 @@ var (
|
||||
ErrServerRespondedWith404 = errors.New("server responded with status 404")
|
||||
ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404")
|
||||
ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410")
|
||||
ErrInvalidWebsocket = errors.New("invalid websocket")
|
||||
)
|
||||
|
||||
type ErrConnectionFailed struct {
|
||||
|
23
vendor/github.com/Rhymen/go-whatsapp/handler.go
generated
vendored
23
vendor/github.com/Rhymen/go-whatsapp/handler.go
generated
vendored
@ -133,6 +133,14 @@ type ChatListHandler interface {
|
||||
HandleChatList(contacts []Chat)
|
||||
}
|
||||
|
||||
/**
|
||||
The BatteryMessageHandler interface needs to be implemented to receive percentage the device connected dispatched by the dispatcher.
|
||||
*/
|
||||
type BatteryMessageHandler interface {
|
||||
Handler
|
||||
HandleBatteryMessage(battery BatteryMessage)
|
||||
}
|
||||
|
||||
/*
|
||||
AddHandler adds an handler to the list of handler that receive dispatched messages.
|
||||
The provided handler must at least implement the Handler interface. Additionally implemented
|
||||
@ -286,6 +294,17 @@ func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handle
|
||||
}
|
||||
}
|
||||
|
||||
case BatteryMessage:
|
||||
for _, h := range handlers {
|
||||
if x, ok := h.(BatteryMessageHandler); ok {
|
||||
if wac.shouldCallSynchronously(h) {
|
||||
x.HandleBatteryMessage(m)
|
||||
} else {
|
||||
go x.HandleBatteryMessage(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *proto.WebMessageInfo:
|
||||
for _, h := range handlers {
|
||||
if x, ok := h.(RawMessageHandler); ok {
|
||||
@ -379,6 +398,10 @@ func (wac *Conn) dispatch(msg interface{}) {
|
||||
wac.handle(ParseProtoMessage(v))
|
||||
}
|
||||
}
|
||||
} else if con, ok := message.Content.([]binary.Node); ok {
|
||||
for a := range con {
|
||||
wac.handle(ParseNodeMessage(con[a]))
|
||||
}
|
||||
}
|
||||
} else if message.Description == "response" && message.Attributes["type"] == "contacts" {
|
||||
wac.updateContacts(message.Content)
|
||||
|
21
vendor/github.com/Rhymen/go-whatsapp/media.go
generated
vendored
21
vendor/github.com/Rhymen/go-whatsapp/media.go
generated
vendored
@ -93,18 +93,21 @@ func downloadMedia(url string) (file []byte, mac []byte, err error) {
|
||||
return data[:n-10], data[n-10 : n], nil
|
||||
}
|
||||
|
||||
|
||||
type MediaConn struct {
|
||||
Status int `json:"status"`
|
||||
MediaConn struct {
|
||||
Auth string `json:"auth"`
|
||||
TTL int `json:"ttl"`
|
||||
Hosts []struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IPs []string `json:"ips"`
|
||||
} `json:"hosts"`
|
||||
} `json:"media_conn"`
|
||||
Status int `json:"status"`
|
||||
MediaConn struct {
|
||||
Auth string `json:"auth"`
|
||||
TTL int `json:"ttl"`
|
||||
Hosts []struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IPs []interface{} `json:"ips"`
|
||||
} `json:"hosts"`
|
||||
} `json:"media_conn"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (wac *Conn) queryMediaConn() (hostname, auth string, ttl int, err error) {
|
||||
queryReq := []interface{}{"query", "mediaConn"}
|
||||
ch, err := wac.writeJson(queryReq)
|
||||
|
136
vendor/github.com/Rhymen/go-whatsapp/message.go
generated
vendored
136
vendor/github.com/Rhymen/go-whatsapp/message.go
generated
vendored
@ -81,7 +81,7 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
|
||||
return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err)
|
||||
}
|
||||
if int(resp["status"].(float64)) != 200 {
|
||||
return "ERROR", fmt.Errorf("message sending responded with %d", resp["status"])
|
||||
return "ERROR", fmt.Errorf("message sending responded with %v", resp["status"])
|
||||
}
|
||||
if int(resp["status"].(float64)) == 200 {
|
||||
return getMessageInfo(msgProto).Id, nil
|
||||
@ -105,6 +105,105 @@ func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) {
|
||||
return wac.writeBinary(n, message, ignore, p.Key.GetId())
|
||||
}
|
||||
|
||||
// RevokeMessage revokes a message (marks as "message removed") for everyone
|
||||
func (wac *Conn) RevokeMessage(remotejid, msgid string, fromme bool) (revokeid string, err error) {
|
||||
// create a revocation ID (required)
|
||||
rawrevocationID := make([]byte, 10)
|
||||
rand.Read(rawrevocationID)
|
||||
revocationID := strings.ToUpper(hex.EncodeToString(rawrevocationID))
|
||||
//
|
||||
ts := uint64(time.Now().Unix())
|
||||
status := proto.WebMessageInfo_PENDING
|
||||
mtype := proto.ProtocolMessage_REVOKE
|
||||
|
||||
revoker := &proto.WebMessageInfo{
|
||||
Key: &proto.MessageKey{
|
||||
FromMe: &fromme,
|
||||
Id: &revocationID,
|
||||
RemoteJid: &remotejid,
|
||||
},
|
||||
MessageTimestamp: &ts,
|
||||
Message: &proto.Message{
|
||||
ProtocolMessage: &proto.ProtocolMessage{
|
||||
Type: &mtype,
|
||||
Key: &proto.MessageKey{
|
||||
FromMe: &fromme,
|
||||
Id: &msgid,
|
||||
RemoteJid: &remotejid,
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: &status,
|
||||
}
|
||||
if _, err := wac.Send(revoker); err != nil {
|
||||
return revocationID, err
|
||||
}
|
||||
return revocationID, nil
|
||||
}
|
||||
|
||||
// DeleteMessage deletes a single message for the user (removes the msgbox). To
|
||||
// delete the message for everyone, use RevokeMessage
|
||||
func (wac *Conn) DeleteMessage(remotejid, msgid string, fromMe bool) error {
|
||||
ch, err := wac.deleteChatProto(remotejid, msgid, fromMe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not send proto: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case response := <-ch:
|
||||
var resp map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(response), &resp); err != nil {
|
||||
return fmt.Errorf("error decoding deletion response: %v", err)
|
||||
}
|
||||
if int(resp["status"].(float64)) != 200 {
|
||||
return fmt.Errorf("message deletion responded with %v", resp["status"])
|
||||
}
|
||||
if int(resp["status"].(float64)) == 200 {
|
||||
return nil
|
||||
}
|
||||
case <-time.After(wac.msgTimeout):
|
||||
return fmt.Errorf("deleting message timed out")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wac *Conn) deleteChatProto(remotejid, msgid string, fromMe bool) (<-chan string, error) {
|
||||
tag := fmt.Sprintf("%s.--%d", wac.timeTag, wac.msgCount)
|
||||
|
||||
owner := "true"
|
||||
if !fromMe {
|
||||
owner = "false"
|
||||
}
|
||||
n := binary.Node{
|
||||
Description: "action",
|
||||
Attributes: map[string]string{
|
||||
"epoch": strconv.Itoa(wac.msgCount),
|
||||
"type": "set",
|
||||
},
|
||||
Content: []interface{}{
|
||||
binary.Node{
|
||||
Description: "chat",
|
||||
Attributes: map[string]string{
|
||||
"type": "clear",
|
||||
"jid": remotejid,
|
||||
"media": "true",
|
||||
},
|
||||
Content: []binary.Node{
|
||||
{
|
||||
Description: "item",
|
||||
Attributes: map[string]string{
|
||||
"owner": owner,
|
||||
"index": msgid,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return wac.writeBinary(n, chat, expires|skipOffline, tag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
@ -744,3 +843,38 @@ func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
BatteryMessage represents a battery level and charging state.
|
||||
*/
|
||||
type BatteryMessage struct {
|
||||
Plugged bool
|
||||
Powersave bool
|
||||
Percentage int
|
||||
}
|
||||
|
||||
func getBatteryMessage(msg map[string]string) BatteryMessage {
|
||||
plugged, _ := strconv.ParseBool(msg["live"])
|
||||
powersave, _ := strconv.ParseBool(msg["powersave"])
|
||||
percentage, _ := strconv.Atoi(msg["value"])
|
||||
batteryMessage := BatteryMessage{
|
||||
Plugged: plugged,
|
||||
Powersave: powersave,
|
||||
Percentage: percentage,
|
||||
}
|
||||
|
||||
return batteryMessage
|
||||
}
|
||||
|
||||
|
||||
func ParseNodeMessage(msg binary.Node) interface{} {
|
||||
switch msg.Description {
|
||||
case "battery":
|
||||
return getBatteryMessage(msg.Attributes)
|
||||
default:
|
||||
//cannot match message
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
11
vendor/github.com/Rhymen/go-whatsapp/read.go
generated
vendored
11
vendor/github.com/Rhymen/go-whatsapp/read.go
generated
vendored
@ -5,13 +5,14 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp/binary"
|
||||
"github.com/Rhymen/go-whatsapp/crypto/cbc"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (wac *Conn) readPump() {
|
||||
@ -27,7 +28,9 @@ func (wac *Conn) readPump() {
|
||||
for {
|
||||
readerFound := make(chan struct{})
|
||||
go func() {
|
||||
msgType, reader, readErr = wac.ws.conn.NextReader()
|
||||
if wac.ws != nil {
|
||||
msgType, reader, readErr = wac.ws.conn.NextReader()
|
||||
}
|
||||
close(readerFound)
|
||||
}()
|
||||
select {
|
||||
|
24
vendor/github.com/Rhymen/go-whatsapp/session.go
generated
vendored
24
vendor/github.com/Rhymen/go-whatsapp/session.go
generated
vendored
@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
//represents the WhatsAppWeb client version
|
||||
var waVersion = []int{0, 4, 2080}
|
||||
var waVersion = []int{2, 2033, 7}
|
||||
|
||||
/*
|
||||
Session contains session individual information. To be able to resume the connection without scanning the qr code
|
||||
@ -141,11 +141,11 @@ func CheckCurrentServerVersion() ([]int, error) {
|
||||
SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the
|
||||
WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible.
|
||||
*/
|
||||
func (wac *Conn) SetClientName(long, short, version string) error {
|
||||
func (wac *Conn) SetClientName(long, short string) error {
|
||||
if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) {
|
||||
return fmt.Errorf("cannot change client name after logging in")
|
||||
}
|
||||
wac.longClientName, wac.shortClientName, wac.clientVersion = long, short, version
|
||||
wac.longClientName, wac.shortClientName = long, short
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -231,7 +231,12 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
|
||||
return session, fmt.Errorf("error decoding login resp: %v\n", err)
|
||||
}
|
||||
|
||||
ref := resp["ref"].(string)
|
||||
var ref string
|
||||
if rref, ok := resp["ref"].(string); ok {
|
||||
ref = rref
|
||||
} else {
|
||||
return session, fmt.Errorf("error decoding login resp: invalid resp['ref']\n")
|
||||
}
|
||||
|
||||
priv, pub, err := curve25519.GenerateKey()
|
||||
if err != nil {
|
||||
@ -390,9 +395,11 @@ func (wac *Conn) Restore() error {
|
||||
}
|
||||
|
||||
if int(resp["status"].(float64)) != 200 {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("init responded with %d", resp["status"])
|
||||
}
|
||||
case <-time.After(wac.msgTimeout):
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("restore session init timed out")
|
||||
}
|
||||
|
||||
@ -401,10 +408,11 @@ func (wac *Conn) Restore() error {
|
||||
select {
|
||||
case r1 := <-s1:
|
||||
if err := json.Unmarshal([]byte(r1), &connResp); err != nil {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("error decoding s1 message: %v\n", err)
|
||||
}
|
||||
case <-time.After(wac.msgTimeout):
|
||||
|
||||
wac.timeTag = ""
|
||||
//check for an error message
|
||||
select {
|
||||
case r := <-loginChan:
|
||||
@ -429,15 +437,18 @@ func (wac *Conn) Restore() error {
|
||||
wac.listener.Unlock()
|
||||
|
||||
if err := wac.resolveChallenge(connResp[1].(map[string]interface{})["challenge"].(string)); err != nil {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("error resolving challenge: %v\n", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case r := <-s2:
|
||||
if err := json.Unmarshal([]byte(r), &connResp); err != nil {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("error decoding s2 message: %v\n", err)
|
||||
}
|
||||
case <-time.After(wac.msgTimeout):
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("restore session challenge timed out")
|
||||
}
|
||||
}
|
||||
@ -447,13 +458,16 @@ func (wac *Conn) Restore() error {
|
||||
case r := <-loginChan:
|
||||
var resp map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(r), &resp); err != nil {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("error decoding login connResp: %v\n", err)
|
||||
}
|
||||
|
||||
if int(resp["status"].(float64)) != 200 {
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("admin login responded with %d", resp["status"])
|
||||
}
|
||||
case <-time.After(wac.msgTimeout):
|
||||
wac.timeTag = ""
|
||||
return fmt.Errorf("restore session login timed out")
|
||||
}
|
||||
|
||||
|
8
vendor/github.com/Rhymen/go-whatsapp/write.go
generated
vendored
8
vendor/github.com/Rhymen/go-whatsapp/write.go
generated
vendored
@ -30,6 +30,11 @@ func (wac *Conn) writeJson(data []interface{}) (<-chan string, error) {
|
||||
messageTag := fmt.Sprintf("%d.--%d", ts, wac.msgCount)
|
||||
bytes := []byte(fmt.Sprintf("%s,%s", messageTag, d))
|
||||
|
||||
if wac.timeTag == "" {
|
||||
tss := fmt.Sprintf("%d", ts)
|
||||
wac.timeTag = tss[len(tss)-3:]
|
||||
}
|
||||
|
||||
ch, err := wac.write(websocket.TextMessage, messageTag, bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -127,6 +132,9 @@ func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<
|
||||
wac.listener.Unlock()
|
||||
}
|
||||
|
||||
if wac == nil || wac.ws == nil {
|
||||
return nil, ErrInvalidWebsocket
|
||||
}
|
||||
wac.ws.Lock()
|
||||
err := wac.ws.conn.WriteMessage(messageType, data)
|
||||
wac.ws.Unlock()
|
||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -15,7 +15,7 @@ github.com/Philipp15b/go-steam/protocol/steamlang
|
||||
github.com/Philipp15b/go-steam/rwu
|
||||
github.com/Philipp15b/go-steam/socialcache
|
||||
github.com/Philipp15b/go-steam/steamid
|
||||
# github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334
|
||||
# github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819
|
||||
github.com/Rhymen/go-whatsapp
|
||||
github.com/Rhymen/go-whatsapp/binary
|
||||
github.com/Rhymen/go-whatsapp/binary/proto
|
||||
|
Loading…
Reference in New Issue
Block a user