Merge branch 'break'

This commit is contained in:
Tulir Asokan 2021-02-22 15:21:35 +02:00
commit 86e5ecbbfe
45 changed files with 803 additions and 2004 deletions

View file

@ -17,6 +17,7 @@
package main package main
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@ -35,7 +36,6 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
type CommandHandler struct { type CommandHandler struct {
@ -255,7 +255,7 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
handler.log.Debugln("%s successfully joined group %s", ce.User.MXID, jid) handler.log.Debugln("%s successfully joined group %s", ce.User.MXID, jid)
portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(jid)) portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(jid))
if len(portal.MXID) > 0 { if len(portal.MXID) > 0 {
portal.Sync(ce.User, whatsapp.Contact{Jid: portal.Key.JID}) portal.Sync(ce.User, whatsapp.Contact{JID: portal.Key.JID})
ce.Reply("Successfully joined group \"%s\" and synced portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID) ce.Reply("Successfully joined group \"%s\" and synced portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID)
} else { } else {
err = portal.CreateMatrixRoom(ce.User) err = portal.CreateMatrixRoom(ce.User)
@ -412,11 +412,11 @@ func (handler *CommandHandler) CommandLogout(ce *CommandEvent) {
ce.Reply("Unknown error while logging out: %v", err) ce.Reply("Unknown error while logging out: %v", err)
return return
} }
ce.User.Disconnect()
ce.User.removeFromJIDMap() ce.User.removeFromJIDMap()
// TODO this causes a foreign key violation, which should be fixed // TODO this causes a foreign key violation, which should be fixed
//ce.User.JID = "" //ce.User.JID = ""
ce.User.SetSession(nil) ce.User.SetSession(nil)
ce.User.DeleteConnection()
ce.Reply("Logged out successfully.") ce.Reply("Logged out successfully.")
} }
@ -470,9 +470,10 @@ func (handler *CommandHandler) CommandDeleteSession(ce *CommandEvent) {
ce.Reply("Nothing to purge: no session information stored and no active connection.") ce.Reply("Nothing to purge: no session information stored and no active connection.")
return return
} }
ce.User.Disconnect() //ce.User.JID = ""
ce.User.removeFromJIDMap() ce.User.removeFromJIDMap()
ce.User.SetSession(nil) ce.User.SetSession(nil)
ce.User.DeleteConnection()
ce.Reply("Session information purged") ce.Reply("Session information purged")
} }
@ -490,24 +491,21 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
} }
wasConnected := true wasConnected := true
sess, err := ce.User.Conn.Disconnect() err := ce.User.Conn.Disconnect()
if err == whatsapp.ErrNotConnected { if err == whatsapp.ErrNotConnected {
wasConnected = false wasConnected = false
} else if err != nil { } else if err != nil {
ce.User.log.Warnln("Error while disconnecting:", err) ce.User.log.Warnln("Error while disconnecting:", err)
} else {
ce.User.SetSession(&sess)
} }
err = ce.User.Conn.Restore(true) ctx := context.Background()
err = ce.User.Conn.Restore(true, ctx)
if err == whatsapp.ErrInvalidSession { if err == whatsapp.ErrInvalidSession {
if ce.User.Session != nil { if ce.User.Session != nil {
ce.User.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...") ce.User.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
var sess whatsapp.Session ce.User.Conn.SetSession(*ce.User.Session)
sess, err = ce.User.Conn.RestoreWithSession(*ce.User.Session) err = ce.User.Conn.Restore(true, ctx)
if err == nil {
ce.User.SetSession(&sess)
}
} else { } else {
ce.Reply("You are not logged in.") ce.Reply("You are not logged in.")
return return
@ -521,17 +519,11 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
} }
if err != nil { if err != nil {
ce.User.log.Warnln("Error while reconnecting:", err) ce.User.log.Warnln("Error while reconnecting:", err)
if errors.Is(err, whatsapp.ErrRestoreSessionTimeout) {
ce.Reply("Reconnection timed out. Is WhatsApp on your phone reachable?")
} else {
ce.Reply("Unknown error while reconnecting: %v", err) ce.Reply("Unknown error while reconnecting: %v", err)
}
ce.User.log.Debugln("Disconnecting due to failed session restore in reconnect command...") ce.User.log.Debugln("Disconnecting due to failed session restore in reconnect command...")
sess, err = ce.User.Conn.Disconnect() err = ce.User.Conn.Disconnect()
if err != nil { if err != nil {
ce.User.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err) ce.User.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
} else {
ce.User.SetSession(&sess)
} }
return return
} }
@ -554,7 +546,7 @@ func (handler *CommandHandler) CommandDeleteConnection(ce *CommandEvent) {
ce.Reply("You don't have a WhatsApp connection.") ce.Reply("You don't have a WhatsApp connection.")
return return
} }
ce.User.Disconnect() ce.User.DeleteConnection()
ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.") ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.")
} }
@ -565,7 +557,7 @@ func (handler *CommandHandler) CommandDisconnect(ce *CommandEvent) {
ce.Reply("You don't have a WhatsApp connection.") ce.Reply("You don't have a WhatsApp connection.")
return return
} }
sess, err := ce.User.Conn.Disconnect() err := ce.User.Conn.Disconnect()
if err == whatsapp.ErrNotConnected { if err == whatsapp.ErrNotConnected {
ce.Reply("You were not connected.") ce.Reply("You were not connected.")
return return
@ -573,8 +565,6 @@ func (handler *CommandHandler) CommandDisconnect(ce *CommandEvent) {
ce.User.log.Warnln("Error while disconnecting:", err) ce.User.log.Warnln("Error while disconnecting:", err)
ce.Reply("Unknown error while disconnecting: %v", err) ce.Reply("Unknown error while disconnecting: %v", err)
return return
} else {
ce.User.SetSession(&sess)
} }
ce.User.bridge.Metrics.TrackConnectionState(ce.User.JID, false) ce.User.bridge.Metrics.TrackConnectionState(ce.User.JID, false)
ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.") ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.")
@ -737,14 +727,14 @@ const cmdListHelp = `list <contacts|groups> [page] [items per page] - Get a list
func formatContacts(contacts bool, input map[string]whatsapp.Contact) (result []string) { func formatContacts(contacts bool, input map[string]whatsapp.Contact) (result []string) {
for jid, contact := range input { for jid, contact := range input {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) != contacts { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) != contacts {
continue continue
} }
if contacts { if contacts {
result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.Jid[:len(contact.Jid)-len(whatsappExt.NewUserSuffix)])) result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.JID[:len(contact.JID)-len(whatsapp.NewUserSuffix)]))
} else { } else {
result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.Jid)) result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.JID))
} }
} }
sort.Sort(sort.StringSlice(result)) sort.Sort(sort.StringSlice(result))
@ -818,8 +808,8 @@ func (handler *CommandHandler) CommandOpen(ce *CommandEvent) {
user := ce.User user := ce.User
jid := ce.Args[0] jid := ce.Args[0]
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
ce.Reply("That looks like a user JID. Did you mean `pm %s`?", jid[:len(jid)-len(whatsappExt.NewUserSuffix)]) ce.Reply("That looks like a user JID. Did you mean `pm %s`?", jid[:len(jid)-len(whatsapp.NewUserSuffix)])
return return
} }
@ -865,7 +855,7 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
return return
} }
} }
jid := number + whatsappExt.NewUserSuffix jid := number + whatsapp.NewUserSuffix
handler.log.Debugln("Importing", jid, "for", user) handler.log.Debugln("Importing", jid, "for", user)
@ -876,11 +866,11 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
"To create a portal anyway, use `pm --force <number>`.") "To create a portal anyway, use `pm --force <number>`.")
return return
} }
contact = whatsapp.Contact{Jid: jid} contact = whatsapp.Contact{JID: jid}
} }
puppet := user.bridge.GetPuppetByJID(contact.Jid) puppet := user.bridge.GetPuppetByJID(contact.JID)
puppet.Sync(user, contact) puppet.Sync(user, contact)
portal := user.bridge.GetPortalByJID(database.NewPortalKey(contact.Jid, user.JID)) portal := user.bridge.GetPortalByJID(database.NewPortalKey(contact.JID, user.JID))
if len(portal.MXID) > 0 { if len(portal.MXID) > 0 {
err := portal.MainIntent().EnsureInvited(portal.MXID, user.MXID) err := portal.MainIntent().EnsureInvited(portal.MXID, user.MXID)
if err != nil { if err != nil {

View file

@ -26,8 +26,6 @@ import (
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/types"
) )
type BridgeConfig struct { type BridgeConfig struct {
@ -167,8 +165,8 @@ type UsernameTemplateArgs struct {
func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8) { func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8) {
var buf bytes.Buffer var buf bytes.Buffer
if index := strings.IndexRune(contact.Jid, '@'); index > 0 { if index := strings.IndexRune(contact.JID, '@'); index > 0 {
contact.Jid = "+" + contact.Jid[:index] contact.JID = "+" + contact.JID[:index]
} }
bc.displaynameTemplate.Execute(&buf, contact) bc.displaynameTemplate.Execute(&buf, contact)
var quality int8 var quality int8
@ -177,7 +175,7 @@ func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8
quality = 3 quality = 3
case len(contact.Name) > 0 || len(contact.Short) > 0: case len(contact.Name) > 0 || len(contact.Short) > 0:
quality = 2 quality = 2
case len(contact.Jid) > 0: case len(contact.JID) > 0:
quality = 1 quality = 1
default: default:
quality = 0 quality = 0
@ -185,7 +183,7 @@ func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8
return buf.String(), quality return buf.String(), quality
} }
func (bc BridgeConfig) FormatUsername(userID types.WhatsAppID) string { func (bc BridgeConfig) FormatUsername(userID whatsapp.JID) string {
var buf bytes.Buffer var buf bytes.Buffer
bc.usernameTemplate.Execute(&buf, userID) bc.usernameTemplate.Execute(&buf, userID)
return buf.String() return buf.String()

View file

@ -164,7 +164,7 @@ func (puppet *Puppet) ProcessResponse(resp *mautrix.RespSync, _ string) error {
} }
for roomID, events := range resp.Rooms.Join { for roomID, events := range resp.Rooms.Join {
portal := puppet.bridge.GetPortalByMXID(roomID) portal := puppet.bridge.GetPortalByMXID(roomID)
if portal == nil { if portal == nil || portal.IsBroadcastList() {
continue continue
} }
for _, evt := range events.Ephemeral.Events { for _, evt := range events.Ephemeral.Events {

View file

@ -22,11 +22,11 @@ import (
"encoding/json" "encoding/json"
"strings" "strings"
"github.com/Rhymen/go-whatsapp"
waProto "github.com/Rhymen/go-whatsapp/binary/proto" waProto "github.com/Rhymen/go-whatsapp/binary/proto"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
@ -43,7 +43,7 @@ func (mq *MessageQuery) New() *Message {
} }
func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) { func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) {
rows, err := mq.db.Query("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content FROM message WHERE chat_jid=$1 AND chat_receiver=$2", chat.JID, chat.Receiver) rows, err := mq.db.Query("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, content FROM message WHERE chat_jid=$1 AND chat_receiver=$2", chat.JID, chat.Receiver)
if err != nil || rows == nil { if err != nil || rows == nil {
return nil return nil
} }
@ -54,19 +54,20 @@ func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) {
return return
} }
func (mq *MessageQuery) GetByJID(chat PortalKey, jid types.WhatsAppMessageID) *Message { func (mq *MessageQuery) GetByJID(chat PortalKey, jid whatsapp.MessageID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, content "+
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", chat.JID, chat.Receiver, jid) "FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", chat.JID, chat.Receiver, jid)
} }
func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message { func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, content "+
"FROM message WHERE mxid=$1", mxid) "FROM message WHERE mxid=$1", mxid)
} }
func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message { func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message {
msg := mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, content " + msg := mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, content "+
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 ORDER BY timestamp DESC LIMIT 1", chat.JID, chat.Receiver) "FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND sent=true ORDER BY timestamp DESC LIMIT 1",
chat.JID, chat.Receiver)
if msg == nil || msg.Timestamp == 0 { if msg == nil || msg.Timestamp == 0 {
// Old db, we don't know what the last message is. // Old db, we don't know what the last message is.
return nil return nil
@ -87,10 +88,11 @@ type Message struct {
log log.Logger log log.Logger
Chat PortalKey Chat PortalKey
JID types.WhatsAppMessageID JID whatsapp.MessageID
MXID id.EventID MXID id.EventID
Sender types.WhatsAppID Sender whatsapp.JID
Timestamp uint64 Timestamp uint64
Sent bool
Content *waProto.Message Content *waProto.Message
} }
@ -100,7 +102,7 @@ func (msg *Message) IsFakeMXID() bool {
func (msg *Message) Scan(row Scannable) *Message { func (msg *Message) Scan(row Scannable) *Message {
var content []byte var content []byte
err := row.Scan(&msg.Chat.JID, &msg.Chat.Receiver, &msg.JID, &msg.MXID, &msg.Sender, &msg.Timestamp, &content) err := row.Scan(&msg.Chat.JID, &msg.Chat.Receiver, &msg.JID, &msg.MXID, &msg.Sender, &msg.Timestamp, &msg.Sent, &content)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
msg.log.Errorln("Database scan failed:", err) msg.log.Errorln("Database scan failed:", err)
@ -134,14 +136,23 @@ func (msg *Message) encodeBinaryContent() []byte {
} }
func (msg *Message) Insert() { func (msg *Message) Insert() {
_, err := msg.db.Exec("INSERT INTO message (chat_jid, chat_receiver, jid, mxid, sender, timestamp, content) " + _, err := msg.db.Exec(`INSERT INTO message
"VALUES ($1, $2, $3, $4, $5, $6, $7)", (chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, content)
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, msg.Sender, msg.Timestamp, msg.encodeBinaryContent()) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, msg.Sender, msg.Timestamp, msg.Sent, msg.encodeBinaryContent())
if err != nil { if err != nil {
msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err) msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err)
} }
} }
func (msg *Message) MarkSent() {
msg.Sent = true
_, err := msg.db.Exec("UPDATE message SET sent=true WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID)
if err != nil {
msg.log.Warnfln("Failed to update %s@%s: %v", msg.Chat, msg.JID, err)
}
}
func (msg *Message) Delete() { func (msg *Message) Delete() {
_, err := msg.db.Exec("DELETE FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID) _, err := msg.db.Exec("DELETE FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID)
if err != nil { if err != nil {

View file

@ -22,25 +22,25 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix-whatsapp/types" "maunium.net/go/mautrix/id"
) )
type PortalKey struct { type PortalKey struct {
JID types.WhatsAppID JID whatsapp.JID
Receiver types.WhatsAppID Receiver whatsapp.JID
} }
func GroupPortalKey(jid types.WhatsAppID) PortalKey { func GroupPortalKey(jid whatsapp.JID) PortalKey {
return PortalKey{ return PortalKey{
JID: jid, JID: jid,
Receiver: jid, Receiver: jid,
} }
} }
func NewPortalKey(jid, receiver types.WhatsAppID) PortalKey { func NewPortalKey(jid, receiver whatsapp.JID) PortalKey {
if strings.HasSuffix(jid, "@g.us") { if strings.HasSuffix(jid, whatsapp.GroupSuffix) {
receiver = jid receiver = jid
} }
return PortalKey{ return PortalKey{
@ -80,11 +80,11 @@ func (pq *PortalQuery) GetByMXID(mxid id.RoomID) *Portal {
return pq.get("SELECT * FROM portal WHERE mxid=$1", mxid) return pq.get("SELECT * FROM portal WHERE mxid=$1", mxid)
} }
func (pq *PortalQuery) GetAllByJID(jid types.WhatsAppID) []*Portal { func (pq *PortalQuery) GetAllByJID(jid whatsapp.JID) []*Portal {
return pq.getAll("SELECT * FROM portal WHERE jid=$1", jid) return pq.getAll("SELECT * FROM portal WHERE jid=$1", jid)
} }
func (pq *PortalQuery) FindPrivateChats(receiver types.WhatsAppID) []*Portal { func (pq *PortalQuery) FindPrivateChats(receiver whatsapp.JID) []*Portal {
return pq.getAll("SELECT * FROM portal WHERE receiver=$1 AND jid LIKE '%@s.whatsapp.net'", receiver) return pq.getAll("SELECT * FROM portal WHERE receiver=$1 AND jid LIKE '%@s.whatsapp.net'", receiver)
} }

View file

@ -21,9 +21,9 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix-whatsapp/types" "maunium.net/go/mautrix/id"
) )
type PuppetQuery struct { type PuppetQuery struct {
@ -53,7 +53,7 @@ func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
return return
} }
func (pq *PuppetQuery) Get(jid types.WhatsAppID) *Puppet { func (pq *PuppetQuery) Get(jid whatsapp.JID) *Puppet {
row := pq.db.QueryRow("SELECT jid, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE jid=$1", jid) row := pq.db.QueryRow("SELECT jid, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE jid=$1", jid)
if row == nil { if row == nil {
return nil return nil
@ -85,7 +85,7 @@ type Puppet struct {
db *Database db *Database
log log.Logger log log.Logger
JID types.WhatsAppID JID whatsapp.JID
Avatar string Avatar string
AvatarURL id.ContentURI AvatarURL id.ContentURI
Displayname string Displayname string

View file

@ -0,0 +1,12 @@
package upgrades
import (
"database/sql"
)
func init() {
upgrades[20] = upgrade{"Add sent column for messages", func(tx *sql.Tx, ctx context) error {
_, err := tx.Exec(`ALTER TABLE message ADD COLUMN sent BOOLEAN NOT NULL DEFAULT true`)
return err
}}
}

View file

@ -39,7 +39,7 @@ type upgrade struct {
fn upgradeFunc fn upgradeFunc
} }
const NumberOfUpgrades = 20 const NumberOfUpgrades = 21
var upgrades [NumberOfUpgrades]upgrade var upgrades [NumberOfUpgrades]upgrade

View file

@ -26,8 +26,6 @@ import (
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
@ -63,7 +61,7 @@ func (uq *UserQuery) GetByMXID(userID id.UserID) *User {
return uq.New().Scan(row) return uq.New().Scan(row)
} }
func (uq *UserQuery) GetByJID(userID types.WhatsAppID) *User { func (uq *UserQuery) GetByJID(userID whatsapp.JID) *User {
row := uq.db.QueryRow(`SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE jid=$1`, stripSuffix(userID)) row := uq.db.QueryRow(`SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE jid=$1`, stripSuffix(userID))
if row == nil { if row == nil {
return nil return nil
@ -76,7 +74,7 @@ type User struct {
log log.Logger log log.Logger
MXID id.UserID MXID id.UserID
JID types.WhatsAppID JID whatsapp.JID
ManagementRoom id.RoomID ManagementRoom id.RoomID
Session *whatsapp.Session Session *whatsapp.Session
LastConnection uint64 LastConnection uint64
@ -93,14 +91,14 @@ func (user *User) Scan(row Scannable) *User {
return nil return nil
} }
if len(jid.String) > 0 && len(clientID.String) > 0 { if len(jid.String) > 0 && len(clientID.String) > 0 {
user.JID = jid.String + whatsappExt.NewUserSuffix user.JID = jid.String + whatsapp.NewUserSuffix
user.Session = &whatsapp.Session{ user.Session = &whatsapp.Session{
ClientId: clientID.String, ClientID: clientID.String,
ClientToken: clientToken.String, ClientToken: clientToken.String,
ServerToken: serverToken.String, ServerToken: serverToken.String,
EncKey: encKey, EncKey: encKey,
MacKey: macKey, MacKey: macKey,
Wid: jid.String + whatsappExt.OldUserSuffix, Wid: jid.String + whatsapp.OldUserSuffix,
} }
} else { } else {
user.Session = nil user.Session = nil
@ -108,7 +106,7 @@ func (user *User) Scan(row Scannable) *User {
return user return user
} }
func stripSuffix(jid types.WhatsAppID) string { func stripSuffix(jid whatsapp.JID) string {
if len(jid) == 0 { if len(jid) == 0 {
return jid return jid
} }
@ -141,7 +139,7 @@ func (user *User) Insert() {
_, err := user.db.Exec(`INSERT INTO "user" (mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, _, err := user.db.Exec(`INSERT INTO "user" (mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
user.MXID, user.jidPtr(), user.MXID, user.jidPtr(),
user.ManagementRoom, user.LastConnection, user.ManagementRoom, user.LastConnection,
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey) sess.ClientID, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey)
if err != nil { if err != nil {
user.log.Warnfln("Failed to insert %s: %v", user.MXID, err) user.log.Warnfln("Failed to insert %s: %v", user.MXID, err)
} }
@ -160,7 +158,7 @@ func (user *User) Update() {
sess := user.sessionUnptr() sess := user.sessionUnptr()
_, err := user.db.Exec(`UPDATE "user" SET jid=$1, management_room=$2, last_connection=$3, client_id=$4, client_token=$5, server_token=$6, enc_key=$7, mac_key=$8 WHERE mxid=$9`, _, err := user.db.Exec(`UPDATE "user" SET jid=$1, management_room=$2, last_connection=$3, client_id=$4, client_token=$5, server_token=$6, enc_key=$7, mac_key=$8 WHERE mxid=$9`,
user.jidPtr(), user.ManagementRoom, user.LastConnection, user.jidPtr(), user.ManagementRoom, user.LastConnection,
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.ClientID, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey,
user.MXID) user.MXID)
if err != nil { if err != nil {
user.log.Warnfln("Failed to update %s: %v", user.MXID, err) user.log.Warnfln("Failed to update %s: %v", user.MXID, err)

View file

@ -22,12 +22,11 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)") var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)")
@ -58,9 +57,9 @@ func NewFormatter(bridge *Bridge) *Formatter {
if mxid[0] == '@' { if mxid[0] == '@' {
puppet := bridge.GetPuppetByMXID(id.UserID(mxid)) puppet := bridge.GetPuppetByMXID(id.UserID(mxid))
if puppet != nil { if puppet != nil {
jids, ok := ctx[mentionedJIDsContextKey].([]types.WhatsAppID) jids, ok := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
if !ok { if !ok {
ctx[mentionedJIDsContextKey] = []types.WhatsAppID{puppet.JID} ctx[mentionedJIDsContextKey] = []whatsapp.JID{puppet.JID}
} else { } else {
ctx[mentionedJIDsContextKey] = append(jids, puppet.JID) ctx[mentionedJIDsContextKey] = append(jids, puppet.JID)
} }
@ -105,7 +104,7 @@ func NewFormatter(bridge *Bridge) *Formatter {
return formatter return formatter
} }
func (formatter *Formatter) getMatrixInfoByJID(jid types.WhatsAppID) (mxid id.UserID, displayname string) { func (formatter *Formatter) getMatrixInfoByJID(jid whatsapp.JID) (mxid id.UserID, displayname string) {
if user := formatter.bridge.GetUserByJID(jid); user != nil { if user := formatter.bridge.GetUserByJID(jid); user != nil {
mxid = user.MXID mxid = user.MXID
displayname = string(user.MXID) displayname = string(user.MXID)
@ -116,7 +115,7 @@ func (formatter *Formatter) getMatrixInfoByJID(jid types.WhatsAppID) (mxid id.Us
return return
} }
func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []types.WhatsAppID) { func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []whatsapp.JID) {
output := html.EscapeString(content.Body) output := html.EscapeString(content.Body)
for regex, replacement := range formatter.waReplString { for regex, replacement := range formatter.waReplString {
output = regex.ReplaceAllString(output, replacement) output = regex.ReplaceAllString(output, replacement)
@ -126,7 +125,7 @@ func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, me
} }
for _, jid := range mentionedJIDs { for _, jid := range mentionedJIDs {
mxid, displayname := formatter.getMatrixInfoByJID(jid) mxid, displayname := formatter.getMatrixInfoByJID(jid)
number := "@" + strings.Replace(jid, whatsappExt.NewUserSuffix, "", 1) number := "@" + strings.Replace(jid, whatsapp.NewUserSuffix, "", 1)
output = strings.Replace(output, number, fmt.Sprintf(`<a href="https://matrix.to/#/%s">%s</a>`, mxid, displayname), -1) output = strings.Replace(output, number, fmt.Sprintf(`<a href="https://matrix.to/#/%s">%s</a>`, mxid, displayname), -1)
content.Body = strings.Replace(content.Body, number, displayname, -1) content.Body = strings.Replace(content.Body, number, displayname, -1)
} }
@ -140,9 +139,9 @@ func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, me
} }
} }
func (formatter *Formatter) ParseMatrix(html string) (string, []types.WhatsAppID) { func (formatter *Formatter) ParseMatrix(html string) (string, []whatsapp.JID) {
ctx := make(format.Context) ctx := make(format.Context)
result := formatter.matrixHTMLParser.Parse(html, ctx) result := formatter.matrixHTMLParser.Parse(html, ctx)
mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]types.WhatsAppID) mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
return result, mentionedJIDs return result, mentionedJIDs
} }

12
go.mod
View file

@ -5,15 +5,15 @@ go 1.14
require ( require (
github.com/Rhymen/go-whatsapp v0.1.0 github.com/Rhymen/go-whatsapp v0.1.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/lib/pq v1.7.0 github.com/lib/pq v1.9.0
github.com/mattn/go-sqlite3 v1.14.0 github.com/mattn/go-sqlite3 v1.14.6
github.com/prometheus/client_golang v1.7.0 github.com/prometheus/client_golang v1.9.0
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
maunium.net/go/mauflag v1.0.0 maunium.net/go/mauflag v1.0.0
maunium.net/go/maulogger/v2 v2.1.1 maunium.net/go/maulogger/v2 v2.2.2
maunium.net/go/mautrix v0.8.2 maunium.net/go/mautrix v0.8.3
) )
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.21 replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.4.0-rc.1

344
go.sum
View file

@ -1,16 +1,31 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
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/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@ -21,23 +36,52 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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/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/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 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-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -48,161 +92,332 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 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.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.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.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.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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/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.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/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-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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-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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= 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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
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/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tulir/go-whatsapp v0.3.13 h1:RPc/GdZ7KlhlGiZp2Zk7B/OP9v0l7ywOt5I2kKAZ+xU= github.com/tulir/go-whatsapp v0.4.0-rc.1 h1:pYaw/V1gzXmchO3wcjI3+LJvNAxsiw2EysG5dUzufCI=
github.com/tulir/go-whatsapp v0.3.13/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= github.com/tulir/go-whatsapp v0.4.0-rc.1/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
github.com/tulir/go-whatsapp v0.3.14 h1:VKXBMw6GvGKRQTceEQ9Dcg6wR+jRy5/G8SDPbisWZqs= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/tulir/go-whatsapp v0.3.14/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/tulir/go-whatsapp v0.3.15 h1:Ogu+f5hvB6Fbdjl6BG7nUb5wuJGCzUa9Z1FraS23M50= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/tulir/go-whatsapp v0.3.15/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
github.com/tulir/go-whatsapp v0.3.16 h1:NfcXC2DQXwls3qkAjbFqSeoMX+rUbbpBBGGvCXI3RUw= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
github.com/tulir/go-whatsapp v0.3.16/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
github.com/tulir/go-whatsapp v0.3.17 h1:HMRT6HzP1seUt5P0waD8CxThB2bfBgKX2uVjOoXCaf8= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
github.com/tulir/go-whatsapp v0.3.17/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
github.com/tulir/go-whatsapp v0.3.18 h1:45pkdjEnAp6yV4RTSWCZn2Eenep+MN7Kndf/rRXQtyY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
github.com/tulir/go-whatsapp v0.3.18/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
github.com/tulir/go-whatsapp v0.3.19 h1:76VtmcjKGX8MbfJN9NNi1f0IVmigTLUcxqE1VRcovcQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
github.com/tulir/go-whatsapp v0.3.19/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
github.com/tulir/go-whatsapp v0.3.20 h1:nK92MgruqXwk+QlaAS39xhzHNbFvJIEgUIOUrN3i8Yc= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
github.com/tulir/go-whatsapp v0.3.20/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
github.com/tulir/go-whatsapp v0.3.21 h1:2m7gUw4oHX4kIpMmP9VwCR7KEUK/PHhXLygPFGF9XfI= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
github.com/tulir/go-whatsapp v0.3.21/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-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-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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-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-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-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-20190125091013-d26f9f9a57f3/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-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-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-20190404232315-eb5bcb51f2a3/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-20180828015842-6cd1fcedba52/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-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-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-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-20190311212946-11955173bddd/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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-20180817151627-c66870c02cf8/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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 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/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -216,9 +431,16 @@ google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEG
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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 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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -226,19 +448,15 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= 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=
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.1.1 h1:NAZNc6XUFJzgzfewCzVoGkxNAsblLCSSEdtDuIjP0XA= maunium.net/go/maulogger/v2 v2.2.2 h1:NCw+7Be1GQFm8xXJ4M2C0Q8yLBTx3c5s7UZ4y1anZMU=
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= maunium.net/go/maulogger/v2 v2.2.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.8.0-rc.3 h1:bb18oNxHUmeiJ0V63YTRVGMjgoeLwu+G40l4n42Z5GI= maunium.net/go/mautrix v0.8.3 h1:nKGdARVCf2w7thEN5GEbAjYrlLjKLX44jOdB1h+BV7U=
maunium.net/go/mautrix v0.8.0-rc.3/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY= maunium.net/go/mautrix v0.8.3/go.mod h1:LPbb/DeAmtOPKnGbJazL9g11cO3mMAaEbLE8udd98BU=
maunium.net/go/mautrix v0.8.0-rc.4 h1:3JXoL2JJPE5nh/YSw9sv9dQA9ulma9yHTMOBMBY1xdo= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
maunium.net/go/mautrix v0.8.0-rc.4/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
maunium.net/go/mautrix v0.8.0 h1:G1jlVslNUTWEqaxuatHAMmzTWnGyoCIc4tAF5GpQJd8=
maunium.net/go/mautrix v0.8.0/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
maunium.net/go/mautrix v0.8.1 h1:YvJGy7euB+x6Mz74jJ+G4NiyrLiX9pzmXpnQB9vFONg=
maunium.net/go/mautrix v0.8.1/go.mod h1:KiViCshKBUZwrVRvTOXsJBFfstvR/btxckHUbOPdu54=
maunium.net/go/mautrix v0.8.2 h1:E3NudQ/QolmE/yhHau8iCkbmcq6gCLvoEvukdqPFJu4=
maunium.net/go/mautrix v0.8.2/go.mod h1:KiViCshKBUZwrVRvTOXsJBFfstvR/btxckHUbOPdu54=

View file

@ -1,2 +0,0 @@
[*.{yaml,yml}]
indent_size = 2

View file

@ -1,2 +0,0 @@
charts/*
!*.yaml

View file

@ -1,22 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -1,14 +0,0 @@
apiVersion: v1
name: mautrix-whatsapp
version: 0.1.0
appVersion: "0.1.0"
description: A Matrix-Whatsapp puppeting bridge.
keywords:
- matrix
- bridge
- whatsapp
maintainers:
- name: Tulir Asokan
email: tulir@maunium.net
sources:
- https://github.com/tulir/mautrix-whatsapp

View file

@ -1,6 +0,0 @@
dependencies:
- name: postgresql
repository: https://kubernetes-charts.storage.googleapis.com/
version: 6.5.0
digest: sha256:85139e9d4207e49c11c5f84d7920d0135cffd3d427f3f3638d4e51258990de2a
generated: "2019-10-23T22:11:37.005827507+03:00"

View file

@ -1,5 +0,0 @@
dependencies:
- name: postgresql
version: 6.5.0
repository: https://kubernetes-charts.storage.googleapis.com/
condition: postgresql.enabled

View file

@ -1,12 +0,0 @@
Your registration file is below. Save it into a YAML file and give the path to that file to synapse:
id: {{ .Values.appservice.id }}
as_token: {{ .Values.appservice.asToken }}
hs_token: {{ .Values.appservice.hsToken }}
namespaces:
users:
- exclusive: true
regex: "@{{ .Values.bridge.username_template | replace "{{.}}" ".+"}}:{{ .Values.homeserver.domain }}"
url: {{ .Values.appservice.address }}
sender_localpart: {{ .Values.appservice.botUsername }}
rate_limited: false

View file

@ -1,55 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mautrix-whatsapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mautrix-whatsapp.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mautrix-whatsapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "mautrix-whatsapp.labels" -}}
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
helm.sh/chart: {{ include "mautrix-whatsapp.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "mautrix-whatsapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "mautrix-whatsapp.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View file

@ -1,45 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "mautrix-whatsapp.fullname" . }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ template "mautrix-whatsapp.name" . }}
data:
config.yaml: |
homeserver:
address: {{ .Values.homeserver.address }}
domain: {{ .Values.homeserver.domain }}
appservice:
address: http://{{ include "mautrix-whatsapp.fullname" . }}:{{ .Values.service.port }}
hostname: 0.0.0.0
port: {{ .Values.service.port }}
{{- if .Values.postgresql.enabled }}
database:
type: postgres
uri: "postgres://postgres:{{ .Values.postgresql.postgresqlPassword }}@{{ .Release.Name }}-postgresql/{{ .Values.postgresql.postgresqlDatabase }}?sslmode=disable"
{{- else }}
database:
{{- toYaml .Values.appservice.database | nindent 8 }}
{{- end }}
id: {{ .Values.appservice.id }}
bot:
username: {{ .Values.appservice.botUsername }}
displayname: {{ .Values.appservice.botDisplayname }}
avatar: {{ .Values.appservice.botAvatar }}
as_token: {{ .Values.appservice.asToken }}
hs_token: {{ .Values.appservice.hsToken }}
bridge:
{{- toYaml .Values.bridge | nindent 6 }}
logging:
{{- toYaml .Values.logging | nindent 6 }}
registration.yaml: ""

View file

@ -1,69 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mautrix-whatsapp.fullname" . }}
labels:
{{- include "mautrix-whatsapp.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
{{- if .Values.podAnnotations }}
annotations:
{{- toYaml .Values.podAnnotations | nindent 6 }}
{{- end }}
metadata:
labels:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: {{ template "mautrix-whatsapp.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- mountPath: /data
name: config-volume
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
# livenessProbe:
# httpGet:
# path: /_matrix/mau/live
# port: http
# initialDelaySeconds: 60
# periodSeconds: 5
# readinessProbe:
# httpGet:
# path: /_matrix/mau/ready
# port: http
# initialDelaySeconds: 60
# periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config-volume
configMap:
name: {{ template "mautrix-whatsapp.fullname" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View file

@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mautrix-whatsapp.fullname" . }}
labels:
{{ include "mautrix-whatsapp.labels" . | indent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "mautrix-whatsapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

View file

@ -1,8 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "mautrix-whatsapp.serviceAccountName" . }}
labels:
{{ include "mautrix-whatsapp.labels" . | indent 4 }}
{{- end -}}

View file

@ -1,137 +0,0 @@
image:
repository: dock.mau.dev/tulir/mautrix-whatsapp
tag: latest
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
service:
type: ClusterIP
port: 29318
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
# Postgres pod configs
postgresql:
enabled: true
postgresqlDatabase: mxwa
postgresqlPassword: SET TO RANDOM STRING
persistence:
size: 2Gi
resources:
requests:
memory: 256Mi
cpu: 100m
# Homeserver details
homeserver:
# The address that this appservice can use to connect to the homeserver.
address: https://example.com
# The domain of the homeserver (for MXIDs, etc).
domain: example.com
# Application service host/registration related details
# Changing these values requires regeneration of the registration.
appservice:
id: whatsapp
botUsername: whatsappbot
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
# to leave display name/avatar as-is.
botDisplayname: WhatsApp bridge bot
botAvatar: mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr
# Authentication tokens for AS <-> HS communication.
asToken: SET TO RANDOM STRING
hsToken: SET TO RANDOM STRING
# The keys below can be used to override the configs in the base config:
# https://github.com/tulir/mautrix-whatsapp/blob/master/example-config.yaml
# Note that the "appservice" and "homeserver" sections are above and slightly different than the base.
# Bridge config
bridge:
# Localpart template of MXIDs for WhatsApp users.
# {{.}} is replaced with the phone number of the WhatsApp user.
username_template: whatsapp_{{.}}
# Number of chats to sync for new users.
initial_chat_sync_count: 10
# Number of old messages to fill when creating new portal rooms.
initial_history_fill_count: 20
# Maximum number of chats to sync when recovering from downtime.
# Set to -1 to sync all new chats during downtime.
recovery_chat_sync_limit: -1
# Whether or not to sync history when recovering from downtime.
recovery_history_backfill: true
# Maximum number of seconds since last message in chat to skip
# syncing the chat in any case. This setting will take priority
# over both recovery_chat_sync_limit and initial_chat_sync_count.
# Default is 3 days = 259200 seconds
sync_max_chat_age: 259200
# Whether or not to explicitly set the avatar and room name for private
# chat portal rooms. This can be useful if the previous field works fine,
# but causes room avatar/name bugs.
private_chat_portal_meta: true
# Allow invite permission for user. User can invite any bots to room with whatsapp
# users (private chat and groups)
allow_user_invite: true
# Permissions for using the bridge.
# Permitted values:
# relaybot - Talk through the relaybot (if enabled), no access otherwise
# user - Access to use the bridge to chat with a WhatsApp account.
# admin - User level and some additional administration tools
# Permitted keys:
# * - All Matrix users
# domain - All users on that homeserver
# mxid - Specific user
permissions:
"*": relaybot
"example.com": user
"@admin:example.com": admin
relaybot:
# Whether or not relaybot support is enabled.
enabled: false
# The management room for the bot. This is where all status notifications are posted and
# in this room, you can use `!wa <command>` instead of `!wa relaybot <command>`. Omitting
# the command prefix completely like in user management rooms is not possible.
management: !foo:example.com
# List of users to invite to all created rooms that include the relaybot.
invites: []
# The formats to use when sending messages to WhatsApp via the relaybot.
message_formats:
m.text: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.notice: "<b>{{ .Sender.Displayname }}</b>: {{ .Message }}"
m.emote: "* <b>{{ .Sender.Displayname }}</b> {{ .Message }}"
m.file: "<b>{{ .Sender.Displayname }}</b> sent a file"
m.image: "<b>{{ .Sender.Displayname }}</b> sent an image"
m.audio: "<b>{{ .Sender.Displayname }}</b> sent an audio file"
m.video: "<b>{{ .Sender.Displayname }}</b> sent a video"
m.location: "<b>{{ .Sender.Displayname }}</b> sent a location"
logging:
timestamp_format: Jan _2, 2006 15:04:05
print_level: debug

15
main.go
View file

@ -25,6 +25,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/Rhymen/go-whatsapp"
flag "maunium.net/go/mauflag" flag "maunium.net/go/mauflag"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
@ -36,7 +38,6 @@ import (
"maunium.net/go/mautrix-whatsapp/config" "maunium.net/go/mautrix-whatsapp/config"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/database/upgrades" "maunium.net/go/mautrix-whatsapp/database/upgrades"
"maunium.net/go/mautrix-whatsapp/types"
) )
var ( var (
@ -137,14 +138,14 @@ type Bridge struct {
Metrics *MetricsHandler Metrics *MetricsHandler
usersByMXID map[id.UserID]*User usersByMXID map[id.UserID]*User
usersByJID map[types.WhatsAppID]*User usersByJID map[whatsapp.JID]*User
usersLock sync.Mutex usersLock sync.Mutex
managementRooms map[id.RoomID]*User managementRooms map[id.RoomID]*User
managementRoomsLock sync.Mutex managementRoomsLock sync.Mutex
portalsByMXID map[id.RoomID]*Portal portalsByMXID map[id.RoomID]*Portal
portalsByJID map[database.PortalKey]*Portal portalsByJID map[database.PortalKey]*Portal
portalsLock sync.Mutex portalsLock sync.Mutex
puppets map[types.WhatsAppID]*Puppet puppets map[whatsapp.JID]*Puppet
puppetsByCustomMXID map[id.UserID]*Puppet puppetsByCustomMXID map[id.UserID]*Puppet
puppetsLock sync.Mutex puppetsLock sync.Mutex
} }
@ -163,11 +164,11 @@ type Crypto interface {
func NewBridge() *Bridge { func NewBridge() *Bridge {
bridge := &Bridge{ bridge := &Bridge{
usersByMXID: make(map[id.UserID]*User), usersByMXID: make(map[id.UserID]*User),
usersByJID: make(map[types.WhatsAppID]*User), usersByJID: make(map[whatsapp.JID]*User),
managementRooms: make(map[id.RoomID]*User), managementRooms: make(map[id.RoomID]*User),
portalsByMXID: make(map[id.RoomID]*Portal), portalsByMXID: make(map[id.RoomID]*Portal),
portalsByJID: make(map[database.PortalKey]*Portal), portalsByJID: make(map[database.PortalKey]*Portal),
puppets: make(map[types.WhatsAppID]*Puppet), puppets: make(map[whatsapp.JID]*Puppet),
puppetsByCustomMXID: make(map[id.UserID]*Puppet), puppetsByCustomMXID: make(map[id.UserID]*Puppet),
} }
@ -383,11 +384,9 @@ func (bridge *Bridge) Stop() {
continue continue
} }
bridge.Log.Debugln("Disconnecting", user.MXID) bridge.Log.Debugln("Disconnecting", user.MXID)
sess, err := user.Conn.Disconnect() err := user.Conn.Disconnect()
if err != nil { if err != nil {
bridge.Log.Errorfln("Error while disconnecting %s: %v", user.MXID, err) bridge.Log.Errorfln("Error while disconnecting %s: %v", user.MXID, err)
} else {
user.SetSession(&sess)
} }
} }
} }

View file

@ -254,6 +254,10 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
return return
} }
if mx.shouldIgnoreEvent(evt) {
return
}
user := mx.bridge.GetUserByMXID(evt.Sender) user := mx.bridge.GetUserByMXID(evt.Sender)
if user == nil || !user.Whitelisted || !user.IsConnected() { if user == nil || !user.Whitelisted || !user.IsConnected() {
return return

View file

@ -27,11 +27,12 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
) )
type MetricsHandler struct { type MetricsHandler struct {
@ -56,11 +57,11 @@ type MetricsHandler struct {
unencryptedPrivateCount prometheus.Gauge unencryptedPrivateCount prometheus.Gauge
connected prometheus.Gauge connected prometheus.Gauge
connectedState map[types.WhatsAppID]bool connectedState map[whatsapp.JID]bool
loggedIn prometheus.Gauge loggedIn prometheus.Gauge
loggedInState map[types.WhatsAppID]bool loggedInState map[whatsapp.JID]bool
syncLocked prometheus.Gauge syncLocked prometheus.Gauge
syncLockedState map[types.WhatsAppID]bool syncLockedState map[whatsapp.JID]bool
bufferLength *prometheus.GaugeVec bufferLength *prometheus.GaugeVec
} }
@ -109,17 +110,17 @@ func NewMetricsHandler(address string, log log.Logger, db *database.Database) *M
Name: "bridge_logged_in", Name: "bridge_logged_in",
Help: "Users logged into the bridge", Help: "Users logged into the bridge",
}), }),
loggedInState: make(map[types.WhatsAppID]bool), loggedInState: make(map[whatsapp.JID]bool),
connected: promauto.NewGauge(prometheus.GaugeOpts{ connected: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_connected", Name: "bridge_connected",
Help: "Bridge users connected to WhatsApp", Help: "Bridge users connected to WhatsApp",
}), }),
connectedState: make(map[types.WhatsAppID]bool), connectedState: make(map[whatsapp.JID]bool),
syncLocked: promauto.NewGauge(prometheus.GaugeOpts{ syncLocked: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_sync_locked", Name: "bridge_sync_locked",
Help: "Bridge users locked in post-login sync", Help: "Bridge users locked in post-login sync",
}), }),
syncLockedState: make(map[types.WhatsAppID]bool), syncLockedState: make(map[whatsapp.JID]bool),
bufferLength: promauto.NewGaugeVec(prometheus.GaugeOpts{ bufferLength: promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "bridge_buffer_size", Name: "bridge_buffer_size",
Help: "Number of messages in buffer", Help: "Number of messages in buffer",
@ -149,7 +150,7 @@ func (mh *MetricsHandler) TrackDisconnection(userID id.UserID) {
mh.disconnections.With(prometheus.Labels{"user_id": string(userID)}).Inc() mh.disconnections.With(prometheus.Labels{"user_id": string(userID)}).Inc()
} }
func (mh *MetricsHandler) TrackLoginState(jid types.WhatsAppID, loggedIn bool) { func (mh *MetricsHandler) TrackLoginState(jid whatsapp.JID, loggedIn bool) {
if !mh.running { if !mh.running {
return return
} }
@ -164,7 +165,7 @@ func (mh *MetricsHandler) TrackLoginState(jid types.WhatsAppID, loggedIn bool) {
} }
} }
func (mh *MetricsHandler) TrackConnectionState(jid types.WhatsAppID, connected bool) { func (mh *MetricsHandler) TrackConnectionState(jid whatsapp.JID, connected bool) {
if !mh.running { if !mh.running {
return return
} }
@ -179,7 +180,7 @@ func (mh *MetricsHandler) TrackConnectionState(jid types.WhatsAppID, connected b
} }
} }
func (mh *MetricsHandler) TrackSyncLock(jid types.WhatsAppID, locked bool) { func (mh *MetricsHandler) TrackSyncLock(jid whatsapp.JID, locked bool) {
if !mh.running { if !mh.running {
return return
} }

206
portal.go
View file

@ -40,25 +40,26 @@ import (
"sync" "sync"
"time" "time"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/crypto/attachment"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
waProto "github.com/Rhymen/go-whatsapp/binary/proto" waProto "github.com/Rhymen/go-whatsapp/binary/proto"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/crypto/attachment"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/pushrules" "maunium.net/go/mautrix/pushrules"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
const StatusBroadcastTopic = "WhatsApp status updates from your contacts"
const StatusBroadcastName = "WhatsApp Status Broadcast"
const BroadcastTopic = "WhatsApp broadcast list"
const UnnamedBroadcastName = "Unnamed broadcast list"
func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal { func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
bridge.portalsLock.Lock() bridge.portalsLock.Lock()
defer bridge.portalsLock.Unlock() defer bridge.portalsLock.Unlock()
@ -83,7 +84,7 @@ func (bridge *Bridge) GetAllPortals() []*Portal {
return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAll()) return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAll())
} }
func (bridge *Bridge) GetAllPortalsByJID(jid types.WhatsAppID) []*Portal { func (bridge *Bridge) GetAllPortalsByJID(jid whatsapp.JID) []*Portal {
return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllByJID(jid)) return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllByJID(jid))
} }
@ -131,7 +132,7 @@ func (bridge *Bridge) NewManualPortal(key database.PortalKey) *Portal {
bridge: bridge, bridge: bridge,
log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)), log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)),
recentlyHandled: [recentlyHandledLength]types.WhatsAppMessageID{}, recentlyHandled: [recentlyHandledLength]whatsapp.MessageID{},
messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer),
} }
@ -146,7 +147,7 @@ func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
bridge: bridge, bridge: bridge,
log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)), log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)),
recentlyHandled: [recentlyHandledLength]types.WhatsAppMessageID{}, recentlyHandled: [recentlyHandledLength]whatsapp.MessageID{},
messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer),
} }
@ -171,7 +172,7 @@ type Portal struct {
roomCreateLock sync.Mutex roomCreateLock sync.Mutex
recentlyHandled [recentlyHandledLength]types.WhatsAppMessageID recentlyHandled [recentlyHandledLength]whatsapp.MessageID
recentlyHandledLock sync.Mutex recentlyHandledLock sync.Mutex
recentlyHandledIndex uint8 recentlyHandledIndex uint8
@ -184,6 +185,7 @@ type Portal struct {
messages chan PortalMessage messages chan PortalMessage
isPrivate *bool isPrivate *bool
isBroadcast *bool
hasRelaybot *bool hasRelaybot *bool
} }
@ -256,7 +258,7 @@ func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
portal.HandleLocationMessage(msg.source, data) portal.HandleLocationMessage(msg.source, data)
case whatsapp.StubMessage: case whatsapp.StubMessage:
portal.HandleStubMessage(msg.source, data, isBackfill) portal.HandleStubMessage(msg.source, data, isBackfill)
case whatsappExt.MessageRevocation: case whatsapp.MessageRevocation:
portal.HandleMessageRevoke(msg.source, data) portal.HandleMessageRevoke(msg.source, data)
case FakeMessage: case FakeMessage:
portal.HandleFakeMessage(msg.source, data) portal.HandleFakeMessage(msg.source, data)
@ -265,7 +267,7 @@ func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
} }
} }
func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool { func (portal *Portal) isRecentlyHandled(id whatsapp.MessageID) bool {
start := portal.recentlyHandledIndex start := portal.recentlyHandledIndex
for i := start; i != start; i = (i - 1) % recentlyHandledLength { for i := start; i != start; i = (i - 1) % recentlyHandledLength {
if portal.recentlyHandled[i] == id { if portal.recentlyHandled[i] == id {
@ -275,7 +277,7 @@ func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool {
return false return false
} }
func (portal *Portal) isDuplicate(id types.WhatsAppMessageID) bool { func (portal *Portal) isDuplicate(id whatsapp.MessageID) bool {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, id) msg := portal.bridge.DB.Message.GetByJID(portal.Key, id)
if msg != nil { if msg != nil {
return true return true
@ -287,7 +289,7 @@ func init() {
gob.Register(&waProto.Message{}) gob.Register(&waProto.Message{})
} }
func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo, mxid id.EventID) { func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo, mxid id.EventID, isSent bool) *database.Message {
msg := portal.bridge.DB.Message.New() msg := portal.bridge.DB.Message.New()
msg.Chat = portal.Key msg.Chat = portal.Key
msg.JID = message.GetKey().GetId() msg.JID = message.GetKey().GetId()
@ -304,6 +306,7 @@ func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo,
} }
} }
msg.Content = message.Message msg.Content = message.Message
msg.Sent = isSent
msg.Insert() msg.Insert()
portal.recentlyHandledLock.Lock() portal.recentlyHandledLock.Lock()
@ -311,6 +314,7 @@ func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo,
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
portal.recentlyHandledLock.Unlock() portal.recentlyHandledLock.Unlock()
portal.recentlyHandled[index] = msg.JID portal.recentlyHandled[index] = msg.JID
return msg
} }
func (portal *Portal) getMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI { func (portal *Portal) getMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI {
@ -350,19 +354,56 @@ func (portal *Portal) startHandling(source *User, info whatsapp.MessageInfo) *ap
} }
func (portal *Portal) finishHandling(source *User, message *waProto.WebMessageInfo, mxid id.EventID) { func (portal *Portal) finishHandling(source *User, message *waProto.WebMessageInfo, mxid id.EventID) {
portal.markHandled(source, message, mxid) portal.markHandled(source, message, mxid, true)
portal.sendDeliveryReceipt(mxid) portal.sendDeliveryReceipt(mxid)
portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid) portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid)
} }
func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) { func (portal *Portal) kickExtraUsers(participantMap map[whatsapp.JID]bool) {
members, err := portal.MainIntent().JoinedMembers(portal.MXID)
if err != nil {
portal.log.Warnln("Failed to get member list:", err)
} else {
for member := range members.Joined {
jid, ok := portal.bridge.ParsePuppetMXID(member)
if ok {
_, shouldBePresent := participantMap[jid]
if !shouldBePresent {
_, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{
UserID: member,
Reason: "User had left this WhatsApp chat",
})
if err != nil {
portal.log.Warnfln("Failed to kick user %s who had left: %v", member, err)
}
}
}
}
}
}
func (portal *Portal) SyncBroadcastRecipients(metadata *whatsapp.BroadcastListInfo) {
participantMap := make(map[whatsapp.JID]bool)
for _, recipient := range metadata.Recipients {
participantMap[recipient.JID] = true
puppet := portal.bridge.GetPuppetByJID(recipient.JID)
err := puppet.DefaultIntent().EnsureJoined(portal.MXID)
if err != nil {
portal.log.Warnfln("Failed to make puppet of %s join %s: %v", recipient.JID, portal.MXID, err)
}
}
portal.kickExtraUsers(participantMap)
}
func (portal *Portal) SyncParticipants(metadata *whatsapp.GroupInfo) {
changed := false changed := false
levels, err := portal.MainIntent().PowerLevels(portal.MXID) levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil { if err != nil {
levels = portal.GetBasePowerLevels() levels = portal.GetBasePowerLevels()
changed = true changed = true
} }
participantMap := make(map[string]bool) participantMap := make(map[whatsapp.JID]bool)
for _, participant := range metadata.Participants { for _, participant := range metadata.Participants {
participantMap[participant.JID] = true participantMap[participant.JID] = true
user := portal.bridge.GetUserByJID(participant.JID) user := portal.bridge.GetUserByJID(participant.JID)
@ -391,29 +432,10 @@ func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
portal.log.Errorln("Failed to change power levels:", err) portal.log.Errorln("Failed to change power levels:", err)
} }
} }
members, err := portal.MainIntent().JoinedMembers(portal.MXID) portal.kickExtraUsers(participantMap)
if err != nil {
portal.log.Warnln("Failed to get member list:", err)
} else {
for member := range members.Joined {
jid, ok := portal.bridge.ParsePuppetMXID(member)
if ok {
_, shouldBePresent := participantMap[jid]
if !shouldBePresent {
_, err := portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{
UserID: member,
Reason: "User had left this WhatsApp chat",
})
if err != nil {
portal.log.Warnfln("Failed to kick user %s who had left: %v", member, err)
}
}
}
}
}
} }
func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInfo, updateInfo bool) bool { func (portal *Portal) UpdateAvatar(user *User, avatar *whatsapp.ProfilePicInfo, updateInfo bool) bool {
if avatar == nil || (avatar.Status == 0 && avatar.Tag != "remove" && len(avatar.URL) == 0) { if avatar == nil || (avatar.Status == 0 && avatar.Tag != "remove" && len(avatar.URL) == 0) {
var err error var err error
avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID) avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID)
@ -464,7 +486,10 @@ func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInf
return true return true
} }
func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool { func (portal *Portal) UpdateName(name string, setBy whatsapp.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
if name == "" && portal.IsBroadcastList() {
name = UnnamedBroadcastName
}
if portal.Name != name { if portal.Name != name {
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name) portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
portal.Name = name portal.Name = name
@ -488,7 +513,7 @@ func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *ap
return false return false
} }
func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool { func (portal *Portal) UpdateTopic(topic string, setBy whatsapp.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
if portal.Topic != topic { if portal.Topic != topic {
portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic) portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic)
portal.Topic = topic portal.Topic = topic
@ -515,10 +540,22 @@ func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID, intent *
func (portal *Portal) UpdateMetadata(user *User) bool { func (portal *Portal) UpdateMetadata(user *User) bool {
if portal.IsPrivateChat() { if portal.IsPrivateChat() {
return false return false
} else if portal.IsStatusBroadcastRoom() { } else if portal.IsStatusBroadcastList() {
update := false update := false
update = portal.UpdateName("WhatsApp Status Broadcast", "", nil, false) || update update = portal.UpdateName(StatusBroadcastName, "", nil, false) || update
update = portal.UpdateTopic("WhatsApp status updates from your contacts", "", nil, false) || update update = portal.UpdateTopic(StatusBroadcastTopic, "", nil, false) || update
return update
} else if portal.IsBroadcastList() {
update := false
broadcastMetadata, err := user.Conn.GetBroadcastMetadata(portal.Key.JID)
if err == nil && broadcastMetadata.Status == 200 {
portal.SyncBroadcastRecipients(broadcastMetadata)
update = portal.UpdateName(broadcastMetadata.Name, "", nil, false) || update
} else {
contact, _ := user.Conn.Store.Contacts[portal.Key.JID]
update = portal.UpdateName(contact.Name, "", nil, false) || update
}
update = portal.UpdateTopic(BroadcastTopic, "", nil, false) || update
return update return update
} }
metadata, err := user.Conn.GetGroupMetaData(portal.Key.JID) metadata, err := user.Conn.GetGroupMetaData(portal.Key.JID)
@ -597,13 +634,9 @@ func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
portal.ensureUserInvited(user) portal.ensureUserInvited(user)
} }
if portal.IsPrivateChat() {
return
}
update := false update := false
update = portal.UpdateMetadata(user) || update update = portal.UpdateMetadata(user) || update
if !portal.IsStatusBroadcastRoom() && portal.Avatar == "" { if !portal.IsPrivateChat() && !portal.IsBroadcastList() && portal.Avatar == "" {
update = portal.UpdateAvatar(user, nil, false) || update update = portal.UpdateAvatar(user, nil, false) || update
} }
if update { if update {
@ -739,6 +772,10 @@ func (portal *Portal) BackfillHistory(user *User, lastMessageTime uint64) error
for len(lastMessageID) > 0 { for len(lastMessageID) > 0 {
portal.log.Debugln("Fetching 50 messages of history after", lastMessageID) portal.log.Debugln("Fetching 50 messages of history after", lastMessageID)
resp, err := user.Conn.LoadMessagesAfter(portal.Key.JID, lastMessageID, lastMessageFromMe, 50) resp, err := user.Conn.LoadMessagesAfter(portal.Key.JID, lastMessageID, lastMessageFromMe, 50)
if err == whatsapp.ErrServerRespondedWith404 {
portal.log.Warnln("Got 404 response trying to fetch messages to backfill. Fetching latest messages as fallback.")
resp, err = user.Conn.LoadMessagesBefore(portal.Key.JID, "", true, 50)
}
if err != nil { if err != nil {
return err return err
} }
@ -974,7 +1011,8 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
portal.log.Infoln("Creating Matrix room. Info source:", user.MXID) portal.log.Infoln("Creating Matrix room. Info source:", user.MXID)
var metadata *whatsappExt.GroupInfo var metadata *whatsapp.GroupInfo
var broadcastMetadata *whatsapp.BroadcastListInfo
if portal.IsPrivateChat() { if portal.IsPrivateChat() {
puppet := portal.bridge.GetPuppetByJID(portal.Key.JID) puppet := portal.bridge.GetPuppetByJID(portal.Key.JID)
if portal.bridge.Config.Bridge.PrivateChatPortalMeta { if portal.bridge.Config.Bridge.PrivateChatPortalMeta {
@ -985,9 +1023,22 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
portal.Name = "" portal.Name = ""
} }
portal.Topic = "WhatsApp private chat" portal.Topic = "WhatsApp private chat"
} else if portal.IsStatusBroadcastRoom() { } else if portal.IsStatusBroadcastList() {
portal.Name = "WhatsApp Status Broadcast" portal.Name = "WhatsApp Status Broadcast"
portal.Topic = "WhatsApp status updates from your contacts" portal.Topic = "WhatsApp status updates from your contacts"
} else if portal.IsBroadcastList() {
var err error
broadcastMetadata, err = user.Conn.GetBroadcastMetadata(portal.Key.JID)
if err == nil && broadcastMetadata.Status == 200 {
portal.Name = broadcastMetadata.Name
} else {
contact, _ := user.Conn.Store.Contacts[portal.Key.JID]
portal.Name = contact.Name
}
if len(portal.Name) == 0 {
portal.Name = UnnamedBroadcastName
}
portal.Topic = BroadcastTopic
} else { } else {
var err error var err error
metadata, err = user.Conn.GetGroupMetaData(portal.Key.JID) metadata, err = user.Conn.GetGroupMetaData(portal.Key.JID)
@ -1076,6 +1127,9 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
_ = customPuppet.CustomIntent().EnsureJoined(portal.MXID) _ = customPuppet.CustomIntent().EnsureJoined(portal.MXID)
} }
} }
if broadcastMetadata != nil {
portal.SyncBroadcastRecipients(broadcastMetadata)
}
user.addPortalToCommunity(portal) user.addPortalToCommunity(portal)
if portal.IsPrivateChat() { if portal.IsPrivateChat() {
puppet := user.bridge.GetPuppetByJID(portal.Key.JID) puppet := user.bridge.GetPuppetByJID(portal.Key.JID)
@ -1099,12 +1153,24 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
func (portal *Portal) IsPrivateChat() bool { func (portal *Portal) IsPrivateChat() bool {
if portal.isPrivate == nil { if portal.isPrivate == nil {
val := strings.HasSuffix(portal.Key.JID, whatsappExt.NewUserSuffix) val := strings.HasSuffix(portal.Key.JID, whatsapp.NewUserSuffix)
portal.isPrivate = &val portal.isPrivate = &val
} }
return *portal.isPrivate return *portal.isPrivate
} }
func (portal *Portal) IsBroadcastList() bool {
if portal.isBroadcast == nil {
val := strings.HasSuffix(portal.Key.JID, whatsapp.BroadcastSuffix)
portal.isBroadcast = &val
}
return *portal.isBroadcast
}
func (portal *Portal) IsStatusBroadcastList() bool {
return portal.Key.JID == "status@broadcast"
}
func (portal *Portal) HasRelaybot() bool { func (portal *Portal) HasRelaybot() bool {
if portal.bridge.Relaybot == nil { if portal.bridge.Relaybot == nil {
return false return false
@ -1115,10 +1181,6 @@ func (portal *Portal) HasRelaybot() bool {
return *portal.hasRelaybot return *portal.hasRelaybot
} }
func (portal *Portal) IsStatusBroadcastRoom() bool {
return portal.Key.JID == "status@broadcast"
}
func (portal *Portal) MainIntent() *appservice.IntentAPI { func (portal *Portal) MainIntent() *appservice.IntentAPI {
if portal.IsPrivateChat() { if portal.IsPrivateChat() {
return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent() return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent()
@ -1152,7 +1214,7 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp
return return
} }
func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.MessageRevocation) { func (portal *Portal) HandleMessageRevoke(user *User, message whatsapp.MessageRevocation) {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id) msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id)
if msg == nil || msg.IsFakeMXID() { if msg == nil || msg.IsFakeMXID() {
return return
@ -1281,8 +1343,9 @@ func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessa
} }
func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessage, isBackfill bool) { func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessage, isBackfill bool) {
if portal.bridge.Config.Bridge.ChatMetaSync { if portal.bridge.Config.Bridge.ChatMetaSync && (!portal.IsBroadcastList() || isBackfill) {
// Chat meta sync is enabled, so we use chat update commands and full-syncs instead of message history // Chat meta sync is enabled, so we use chat update commands and full-syncs instead of message history
// However, broadcast lists don't have update commands, so we handle these if it's not a backfill
return return
} }
intent := portal.startHandling(source, message.Info) intent := portal.startHandling(source, message.Info)
@ -1312,9 +1375,9 @@ func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessa
eventID = portal.RestrictMessageSending(message.FirstParam == "on") eventID = portal.RestrictMessageSending(message.FirstParam == "on")
case waProto.WebMessageInfo_GROUP_CHANGE_RESTRICT: case waProto.WebMessageInfo_GROUP_CHANGE_RESTRICT:
eventID = portal.RestrictMetadataChanges(message.FirstParam == "on") eventID = portal.RestrictMetadataChanges(message.FirstParam == "on")
case waProto.WebMessageInfo_GROUP_PARTICIPANT_ADD, waProto.WebMessageInfo_GROUP_PARTICIPANT_INVITE: case waProto.WebMessageInfo_GROUP_PARTICIPANT_ADD, waProto.WebMessageInfo_GROUP_PARTICIPANT_INVITE, waProto.WebMessageInfo_BROADCAST_ADD:
portal.HandleWhatsAppInvite(senderJID, intent, message.Params) portal.HandleWhatsAppInvite(senderJID, intent, message.Params)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_REMOVE, waProto.WebMessageInfo_GROUP_PARTICIPANT_LEAVE: case waProto.WebMessageInfo_GROUP_PARTICIPANT_REMOVE, waProto.WebMessageInfo_GROUP_PARTICIPANT_LEAVE, waProto.WebMessageInfo_BROADCAST_REMOVE:
portal.HandleWhatsAppKick(source, senderJID, message.Params) portal.HandleWhatsAppKick(source, senderJID, message.Params)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_PROMOTE: case waProto.WebMessageInfo_GROUP_PARTICIPANT_PROMOTE:
eventID = portal.ChangeAdminStatus(message.Params, true) eventID = portal.ChangeAdminStatus(message.Params, true)
@ -1326,7 +1389,7 @@ func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessa
if len(eventID) == 0 { if len(eventID) == 0 {
eventID = id.EventID(fmt.Sprintf("net.maunium.whatsapp.fake::%s", message.Info.Id)) eventID = id.EventID(fmt.Sprintf("net.maunium.whatsapp.fake::%s", message.Info.Id))
} }
portal.markHandled(source, message.Info.Source, eventID) portal.markHandled(source, message.Info.Source, eventID, true)
} }
func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) { func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) {
@ -1496,6 +1559,7 @@ func (portal *Portal) HandleWhatsAppKick(source *User, senderJID string, jids []
puppet := portal.bridge.GetPuppetByJID(jid) puppet := portal.bridge.GetPuppetByJID(jid)
portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent()) portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
if !portal.IsBroadcastList() {
user := portal.bridge.GetUserByJID(jid) user := portal.bridge.GetUserByJID(jid)
if user != nil { if user != nil {
var customIntent *appservice.IntentAPI var customIntent *appservice.IntentAPI
@ -1505,6 +1569,7 @@ func (portal *Portal) HandleWhatsAppKick(source *User, senderJID string, jids []
portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent) portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent)
} }
} }
}
} }
func (portal *Portal) HandleWhatsAppInvite(senderJID string, intent *appservice.IntentAPI, jids []string) { func (portal *Portal) HandleWhatsAppInvite(senderJID string, intent *appservice.IntentAPI, jids []string) {
@ -1769,7 +1834,7 @@ func (portal *Portal) convertGifToVideo(gif []byte) ([]byte, error) {
"-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart", "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart",
"-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'",
outputFileName) outputFileName)
vcLog := portal.log.Sub("VideoConverter").WithDefaultLevel(log.LevelWarn) vcLog := portal.log.Sub("VideoConverter").Writer(log.LevelWarn)
cmd.Stdout = vcLog cmd.Stdout = vcLog
cmd.Stderr = vcLog cmd.Stderr = vcLog
@ -1794,7 +1859,7 @@ func (portal *Portal) convertGifToVideo(gif []byte) ([]byte, error) {
func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsapp.MediaType) *MediaUpload { func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsapp.MediaType) *MediaUpload {
var caption string var caption string
var mentionedJIDs []types.WhatsAppID var mentionedJIDs []whatsapp.JID
if relaybotFormatted { if relaybotFormatted {
caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody) caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
} }
@ -1851,7 +1916,7 @@ func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool
type MediaUpload struct { type MediaUpload struct {
Caption string Caption string
MentionedJIDs []types.WhatsAppID MentionedJIDs []whatsapp.JID
URL string URL string
MediaKey []byte MediaKey []byte
FileEncSHA256 []byte FileEncSHA256 []byte
@ -2091,12 +2156,12 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
if info == nil { if info == nil {
return return
} }
portal.markHandled(sender, info, evt.ID) dbMsg := portal.markHandled(sender, info, evt.ID, false)
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.Key.GetId()) portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.Key.GetId())
portal.sendRaw(sender, evt, info) portal.sendRaw(sender, evt, info, dbMsg)
} }
func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebMessageInfo) { func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebMessageInfo, dbMsg *database.Message) {
errChan := make(chan error, 1) errChan := make(chan error, 1)
go sender.Conn.SendRaw(info, errChan) go sender.Conn.SendRaw(info, errChan)
@ -2116,16 +2181,11 @@ func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebM
} }
if err != nil { if err != nil {
portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err) portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err)
var statusResp whatsapp.StatusResponse
if errors.As(err, &statusResp) && statusResp.Status == 599 {
portal.log.Debugfln("599 status response extra data: %+v", statusResp.Extra)
portal.sendErrorMessage(fmt.Sprintf("%v. Please try again after a few minutes", err))
} else {
portal.sendErrorMessage(err.Error()) portal.sendErrorMessage(err.Error())
}
} else { } else {
portal.log.Debugfln("Handled Matrix event %s", evt.ID) portal.log.Debugfln("Handled Matrix event %s", evt.ID)
portal.sendDeliveryReceipt(evt.ID) portal.sendDeliveryReceipt(evt.ID)
dbMsg.MarkSent()
} }
if errorEventID != "" { if errorEventID != "" {
_, err = portal.MainIntent().RedactEvent(portal.MXID, errorEventID) _, err = portal.MainIntent().RedactEvent(portal.MXID, errorEventID)
@ -2322,7 +2382,7 @@ func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
return return
} }
portal.Topic = content.Topic portal.Topic = content.Topic
resp, err = sender.Conn.UpdateGroupDescription(portal.Key.JID, content.Topic) resp, err = sender.Conn.UpdateGroupDescription(sender.JID, portal.Key.JID, content.Topic)
case *event.RoomAvatarEventContent: case *event.RoomAvatarEventContent:
return return
} }

View file

@ -27,12 +27,11 @@ import (
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/id"
whatsappExt "maunium.net/go/mautrix-whatsapp/whatsapp-ext" log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/id"
) )
type ProvisioningAPI struct { type ProvisioningAPI struct {
@ -126,7 +125,7 @@ func (prov *ProvisioningAPI) DeleteSession(w http.ResponseWriter, r *http.Reques
}) })
return return
} }
user.Disconnect() user.DeleteConnection()
user.SetSession(nil) user.SetSession(nil)
jsonResponse(w, http.StatusOK, Response{true, "Session information purged"}) jsonResponse(w, http.StatusOK, Response{true, "Session information purged"})
} }
@ -140,7 +139,7 @@ func (prov *ProvisioningAPI) DeleteConnection(w http.ResponseWriter, r *http.Req
}) })
return return
} }
user.Disconnect() user.DeleteConnection()
jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp and connection deleted"}) jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp and connection deleted"})
} }
@ -153,7 +152,7 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request)
}) })
return return
} }
sess, err := user.Conn.Disconnect() err := user.Conn.Disconnect()
if err == whatsapp.ErrNotConnected { if err == whatsapp.ErrNotConnected {
jsonResponse(w, http.StatusNotFound, Error{ jsonResponse(w, http.StatusNotFound, Error{
Error: "You were not connected", Error: "You were not connected",
@ -167,8 +166,6 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request)
ErrCode: err.Error(), ErrCode: err.Error(),
}) })
return return
} else {
user.SetSession(&sess)
} }
user.bridge.Metrics.TrackConnectionState(user.JID, false) user.bridge.Metrics.TrackConnectionState(user.JID, false)
jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp"}) jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp"})
@ -191,25 +188,21 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
user.log.Debugln("Received /reconnect request, disconnecting") user.log.Debugln("Received /reconnect request, disconnecting")
wasConnected := true wasConnected := true
sess, err := user.Conn.Disconnect() err := user.Conn.Disconnect()
if err == whatsapp.ErrNotConnected { if err == whatsapp.ErrNotConnected {
wasConnected = false wasConnected = false
} else if err != nil { } else if err != nil {
user.log.Warnln("Error while disconnecting:", err) user.log.Warnln("Error while disconnecting:", err)
} else {
user.SetSession(&sess)
} }
user.log.Debugln("Restoring session for /reconnect") user.log.Debugln("Restoring session for /reconnect")
err = user.Conn.Restore(true) err = user.Conn.Restore(true, r.Context())
user.log.Debugfln("Restore session for /reconnect responded with %v", err) user.log.Debugfln("Restore session for /reconnect responded with %v", err)
if err == whatsapp.ErrInvalidSession { if err == whatsapp.ErrInvalidSession {
if user.Session != nil { if user.Session != nil {
user.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...") user.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
sess, err = user.Conn.RestoreWithSession(*user.Session) user.Conn.SetSession(*user.Session)
if err == nil { err = user.Conn.Restore(true, r.Context())
user.SetSession(&sess)
}
} else { } else {
jsonResponse(w, http.StatusForbidden, Error{ jsonResponse(w, http.StatusForbidden, Error{
Error: "You're not logged in", Error: "You're not logged in",
@ -217,7 +210,8 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
}) })
return return
} }
} else if err == whatsapp.ErrLoginInProgress { }
if err == whatsapp.ErrLoginInProgress {
jsonResponse(w, http.StatusConflict, Error{ jsonResponse(w, http.StatusConflict, Error{
Error: "A login or reconnection is already in progress.", Error: "A login or reconnection is already in progress.",
ErrCode: "login in progress", ErrCode: "login in progress",
@ -232,23 +226,14 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
} }
if err != nil { if err != nil {
user.log.Warnln("Error while reconnecting:", err) user.log.Warnln("Error while reconnecting:", err)
if errors.Is(err, whatsapp.ErrRestoreSessionTimeout) { jsonResponse(w, http.StatusInternalServerError, Error{
jsonResponse(w, http.StatusForbidden, Error{
Error: "Reconnection timed out. Is WhatsApp on your phone reachable?",
ErrCode: err.Error(),
})
} else {
jsonResponse(w, http.StatusForbidden, Error{
Error: fmt.Sprintf("Unknown error while reconnecting: %v", err), Error: fmt.Sprintf("Unknown error while reconnecting: %v", err),
ErrCode: err.Error(), ErrCode: err.Error(),
}) })
}
user.log.Debugln("Disconnecting due to failed session restore in reconnect command...") user.log.Debugln("Disconnecting due to failed session restore in reconnect command...")
sess, err := user.Conn.Disconnect() err = user.Conn.Disconnect()
if err != nil { if err != nil {
user.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err) user.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
} else {
user.SetSession(&sess)
} }
return return
} }
@ -340,7 +325,7 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
user.Disconnect() user.DeleteConnection()
} }
user.bridge.Metrics.TrackConnectionState(user.JID, false) user.bridge.Metrics.TrackConnectionState(user.JID, false)
@ -408,7 +393,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
}) })
user.log.Debugln("Starting login via provisioning API") user.log.Debugln("Starting login via provisioning API")
session, err := user.Conn.LoginWithRetry(qrChan, ctx, user.bridge.Config.Bridge.LoginQRRegenCount) session, jid, err := user.Conn.Login(qrChan, ctx, user.bridge.Config.Bridge.LoginQRRegenCount)
qrChan <- "stop" qrChan <- "stop"
if err != nil { if err != nil {
var msg string var msg string
@ -420,7 +405,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
msg = "QR code scan timed out. Please try again." msg = "QR code scan timed out. Please try again."
} else if errors.Is(err, whatsapp.ErrInvalidWebsocket) { } else if errors.Is(err, whatsapp.ErrInvalidWebsocket) {
msg = "WhatsApp connection error. Please try again." msg = "WhatsApp connection error. Please try again."
user.Disconnect() // TODO might need to make sure it reconnects?
} else { } else {
msg = fmt.Sprintf("Unknown error while logging in: %v", err) msg = fmt.Sprintf("Unknown error while logging in: %v", err)
} }
@ -431,9 +416,9 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
}) })
return return
} }
user.log.Debugln("Successful login via provisioning API") user.log.Debugln("Successful login as", jid, "via provisioning API")
user.ConnectionErrors = 0 user.ConnectionErrors = 0
user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1) user.JID = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
user.addToJIDMap() user.addToJIDMap()
user.SetSession(&session) user.SetSession(&session)
_ = c.WriteJSON(map[string]interface{}{ _ = c.WriteJSON(map[string]interface{}{

View file

@ -22,21 +22,18 @@ import (
"regexp" "regexp"
"strings" "strings"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp" "github.com/Rhymen/go-whatsapp"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
var userIDRegex *regexp.Regexp var userIDRegex *regexp.Regexp
func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.WhatsAppID, bool) { func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (whatsapp.JID, bool) {
if userIDRegex == nil { if userIDRegex == nil {
userIDRegex = regexp.MustCompile(fmt.Sprintf("^@%s:%s$", userIDRegex = regexp.MustCompile(fmt.Sprintf("^@%s:%s$",
bridge.Config.Bridge.FormatUsername("([0-9]+)"), bridge.Config.Bridge.FormatUsername("([0-9]+)"),
@ -47,7 +44,7 @@ func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.WhatsAppID, bool) {
return "", false return "", false
} }
jid := types.WhatsAppID(match[1] + whatsappExt.NewUserSuffix) jid := whatsapp.JID(match[1] + whatsapp.NewUserSuffix)
return jid, true return jid, true
} }
@ -60,7 +57,7 @@ func (bridge *Bridge) GetPuppetByMXID(mxid id.UserID) *Puppet {
return bridge.GetPuppetByJID(jid) return bridge.GetPuppetByJID(jid)
} }
func (bridge *Bridge) GetPuppetByJID(jid types.WhatsAppID) *Puppet { func (bridge *Bridge) GetPuppetByJID(jid whatsapp.JID) *Puppet {
bridge.puppetsLock.Lock() bridge.puppetsLock.Lock()
defer bridge.puppetsLock.Unlock() defer bridge.puppetsLock.Unlock()
puppet, ok := bridge.puppets[jid] puppet, ok := bridge.puppets[jid]
@ -125,12 +122,12 @@ func (bridge *Bridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Puppet
return output return output
} }
func (bridge *Bridge) FormatPuppetMXID(jid types.WhatsAppID) id.UserID { func (bridge *Bridge) FormatPuppetMXID(jid whatsapp.JID) id.UserID {
return id.NewUserID( return id.NewUserID(
bridge.Config.Bridge.FormatUsername( bridge.Config.Bridge.FormatUsername(
strings.Replace( strings.Replace(
jid, jid,
whatsappExt.NewUserSuffix, "", 1)), whatsapp.NewUserSuffix, "", 1)),
bridge.Config.Homeserver.Domain) bridge.Config.Homeserver.Domain)
} }
@ -161,7 +158,7 @@ type Puppet struct {
} }
func (puppet *Puppet) PhoneNumber() string { func (puppet *Puppet) PhoneNumber() string {
return strings.Replace(puppet.JID, whatsappExt.NewUserSuffix, "", 1) return strings.Replace(puppet.JID, whatsapp.NewUserSuffix, "", 1)
} }
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI { func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
@ -181,7 +178,7 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
return puppet.bridge.AS.Intent(puppet.MXID) return puppet.bridge.AS.Intent(puppet.MXID)
} }
func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicInfo) bool { func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsapp.ProfilePicInfo) bool {
if avatar == nil { if avatar == nil {
var err error var err error
avatar, err = source.Conn.GetProfilePicThumb(puppet.JID) avatar, err = source.Conn.GetProfilePicThumb(puppet.JID)
@ -294,8 +291,8 @@ func (puppet *Puppet) Sync(source *User, contact whatsapp.Contact) {
puppet.log.Errorln("Failed to ensure registered:", err) puppet.log.Errorln("Failed to ensure registered:", err)
} }
if contact.Jid == source.JID { if contact.JID == source.JID {
contact.Notify = source.Conn.Info.Pushname contact.Notify = source.pushName
} }
update := false update := false

View file

@ -1,23 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package types
// WhatsAppID is a WhatsApp JID.
type WhatsAppID = string
// WhatsAppMessageID is the internal ID of a WhatsApp message.
type WhatsAppMessageID = string

374
user.go
View file

@ -17,6 +17,7 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -40,13 +41,11 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database" "maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
) )
type User struct { type User struct {
*database.User *database.User
Conn *whatsappExt.ExtendedConn Conn *whatsapp.Conn
bridge *Bridge bridge *Bridge
log log.Logger log log.Logger
@ -63,6 +62,7 @@ type User struct {
cleanDisconnection bool cleanDisconnection bool
batteryWarningsSent int batteryWarningsSent int
lastReconnection int64 lastReconnection int64
pushName string
chatListReceived chan struct{} chatListReceived chan struct{}
syncPortalsDone chan struct{} syncPortalsDone chan struct{}
@ -75,6 +75,7 @@ type User struct {
mgmtCreateLock sync.Mutex mgmtCreateLock sync.Mutex
connLock sync.Mutex connLock sync.Mutex
cancelReconnect func()
} }
func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User { func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
@ -91,7 +92,7 @@ func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
return user return user
} }
func (bridge *Bridge) GetUserByJID(userID types.WhatsAppID) *User { func (bridge *Bridge) GetUserByJID(userID whatsapp.JID) *User {
bridge.usersLock.Lock() bridge.usersLock.Lock()
defer bridge.usersLock.Unlock() defer bridge.usersLock.Unlock()
user, ok := bridge.usersByJID[userID] user, ok := bridge.usersByJID[userID]
@ -236,55 +237,56 @@ func (user *User) SetSession(session *whatsapp.Session) {
func (user *User) Connect(evenIfNoSession bool) bool { func (user *User) Connect(evenIfNoSession bool) bool {
user.connLock.Lock() user.connLock.Lock()
if user.Conn != nil && user.Conn.IsConnected() { if user.Conn != nil {
user.connLock.Unlock() user.connLock.Unlock()
if user.Conn.IsConnected() {
return true return true
} else {
return user.RestoreSession()
}
} else if !evenIfNoSession && user.Session == nil { } else if !evenIfNoSession && user.Session == nil {
user.connLock.Unlock() user.connLock.Unlock()
return false return false
} }
if user.Conn != nil {
user.Disconnect()
}
user.log.Debugln("Connecting to WhatsApp") user.log.Debugln("Connecting to WhatsApp")
timeout := time.Duration(user.bridge.Config.Bridge.ConnectionTimeout) timeout := time.Duration(user.bridge.Config.Bridge.ConnectionTimeout)
if timeout == 0 { if timeout == 0 {
timeout = 20 timeout = 20
} }
conn, err := whatsapp.NewConnWithOptions(&whatsapp.Options{ user.Conn = whatsapp.NewConn(&whatsapp.Options{
Timeout: timeout * time.Second, Timeout: timeout * time.Second,
LongClientName: user.bridge.Config.WhatsApp.OSName, LongClientName: user.bridge.Config.WhatsApp.OSName,
ShortClientName: user.bridge.Config.WhatsApp.BrowserName, ShortClientName: user.bridge.Config.WhatsApp.BrowserName,
ClientVersion: WAVersion, ClientVersion: WAVersion,
Log: user.log.Sub("Conn"),
Handler: []whatsapp.Handler{user},
}) })
if err != nil {
user.log.Errorln("Failed to connect to WhatsApp:", err)
user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp server. " +
"This indicates a network problem on the bridge server. See bridge logs for more info.")
user.connLock.Unlock()
return false
}
user.Conn = whatsappExt.ExtendConn(conn)
user.log.Debugln("WhatsApp connection successful")
user.Conn.AddHandler(user)
user.connLock.Unlock() user.connLock.Unlock()
return user.RestoreSession() return user.RestoreSession()
} }
func (user *User) Disconnect() { func (user *User) DeleteConnection() {
sess, err := user.Conn.Disconnect() user.connLock.Lock()
if user.Conn == nil {
user.connLock.Unlock()
return
}
err := user.Conn.Disconnect()
if err != nil && err != whatsapp.ErrNotConnected { if err != nil && err != whatsapp.ErrNotConnected {
user.log.Warnln("Error disconnecting: %v", err) user.log.Warnln("Error disconnecting: %v", err)
} }
user.SetSession(&sess)
user.Conn.RemoveHandlers() user.Conn.RemoveHandlers()
user.Conn = nil user.Conn = nil
user.bridge.Metrics.TrackConnectionState(user.JID, false) user.bridge.Metrics.TrackConnectionState(user.JID, false)
user.connLock.Unlock()
} }
func (user *User) RestoreSession() bool { func (user *User) RestoreSession() bool {
if user.Session != nil { if user.Session != nil {
sess, err := user.Conn.RestoreWithSession(*user.Session) user.Conn.SetSession(*user.Session)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
err := user.Conn.Restore(true, ctx)
if err == whatsapp.ErrAlreadyLoggedIn { if err == whatsapp.ErrAlreadyLoggedIn {
return true return true
} else if err != nil { } else if err != nil {
@ -292,24 +294,23 @@ func (user *User) RestoreSession() bool {
if errors.Is(err, whatsapp.ErrUnpaired) { if errors.Is(err, whatsapp.ErrUnpaired) {
user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " + user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " +
"To re-pair your phone, log in again.") "To re-pair your phone, log in again.")
user.Disconnect()
user.removeFromJIDMap() user.removeFromJIDMap()
//user.JID = "" //user.JID = ""
user.SetSession(nil) user.SetSession(nil)
user.DeleteConnection()
return false return false
} else { } else {
user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " + user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " +
"on your phone is reachable and use `reconnect` to try connecting again.") "on your phone is reachable and use `reconnect` to try connecting again.")
} }
user.log.Debugln("Disconnecting due to failed session restore...") user.log.Debugln("Disconnecting due to failed session restore...")
_, err := user.Conn.Disconnect() err = user.Conn.Disconnect()
if err != nil { if err != nil {
user.log.Errorln("Failed to disconnect after failed session restore:", err) user.log.Errorln("Failed to disconnect after failed session restore:", err)
} }
return false return false
} }
user.ConnectionErrors = 0 user.ConnectionErrors = 0
user.SetSession(&sess)
user.log.Debugln("Session restored successfully") user.log.Debugln("Session restored successfully")
user.PostLogin() user.PostLogin()
} }
@ -384,7 +385,7 @@ func (user *User) Login(ce *CommandEvent) {
qrChan := make(chan string, 3) qrChan := make(chan string, 3)
eventIDChan := make(chan id.EventID, 1) eventIDChan := make(chan id.EventID, 1)
go user.loginQrChannel(ce, qrChan, eventIDChan) go user.loginQrChannel(ce, qrChan, eventIDChan)
session, err := user.Conn.LoginWithRetry(qrChan, nil, user.bridge.Config.Bridge.LoginQRRegenCount) session, jid, err := user.Conn.Login(qrChan, nil, user.bridge.Config.Bridge.LoginQRRegenCount)
qrChan <- "stop" qrChan <- "stop"
if err != nil { if err != nil {
var eventID id.EventID var eventID id.EventID
@ -418,8 +419,9 @@ func (user *User) Login(ce *CommandEvent) {
} }
// TODO there's a bit of duplication between this and the provisioning API login method // TODO there's a bit of duplication between this and the provisioning API login method
// Also between the two logout methods (commands.go and provisioning.go) // Also between the two logout methods (commands.go and provisioning.go)
user.log.Debugln("Successful login as", jid, "via command")
user.ConnectionErrors = 0 user.ConnectionErrors = 0
user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1) user.JID = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
user.addToJIDMap() user.addToJIDMap()
user.SetSession(&session) user.SetSession(&session)
ce.Reply("Successfully logged in, synchronizing chats...") ce.Reply("Successfully logged in, synchronizing chats...")
@ -501,20 +503,24 @@ func (user *User) sendMarkdownBridgeAlert(formatString string, args ...interface
} }
} }
func (user *User) postConnPing(conn *whatsappExt.ExtendedConn) bool { func (user *User) postConnPing() bool {
if user.Conn != conn {
user.log.Warnln("Connection changed before scheduled post-connection ping, canceling ping")
return false
}
user.log.Debugln("Making post-connection ping") user.log.Debugln("Making post-connection ping")
err := conn.AdminTest() var err error
if err != nil { for i := 0; ; i++ {
err = user.Conn.AdminTest()
if err == nil {
user.log.Debugln("Post-connection ping OK")
return true
} else if errors.Is(err, whatsapp.ErrConnectionTimeout) && i < 5 {
user.log.Warnfln("Post-connection ping timed out, sending new one")
} else {
break
}
}
user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err) user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err)
sess, disconnectErr := conn.Disconnect() disconnectErr := user.Conn.Disconnect()
if disconnectErr != nil { if disconnectErr != nil {
user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr) user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr)
} else {
user.Session = &sess
} }
user.bridge.Metrics.TrackDisconnection(user.MXID) user.bridge.Metrics.TrackDisconnection(user.MXID)
go func() { go func() {
@ -522,13 +528,9 @@ func (user *User) postConnPing(conn *whatsappExt.ExtendedConn) bool {
user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err)) user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
}() }()
return false return false
} else {
user.log.Debugln("Post-connection ping OK")
return true
}
} }
func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) { func (user *User) intPostLogin(conn *whatsapp.Conn) {
defer user.syncWait.Done() defer user.syncWait.Done()
user.lastReconnection = time.Now().Unix() user.lastReconnection = time.Now().Unix()
user.createCommunity() user.createCommunity()
@ -540,11 +542,11 @@ func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) {
user.log.Debugln("Chat list receive confirmation received in PostLogin") user.log.Debugln("Chat list receive confirmation received in PostLogin")
case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second): case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second):
user.log.Warnln("Timed out waiting for chat list to arrive!") user.log.Warnln("Timed out waiting for chat list to arrive!")
user.postConnPing(conn) user.postConnPing()
return return
} }
if !user.postConnPing(conn) { if !user.postConnPing() {
user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.") user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.")
return return
} }
@ -559,16 +561,68 @@ func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) {
} }
} }
func (user *User) HandleStreamEvent(evt whatsappExt.StreamEvent) { type NormalMessage interface {
if evt.Type == whatsappExt.StreamSleep { GetInfo() whatsapp.MessageInfo
}
func (user *User) HandleEvent(event interface{}) {
switch v := event.(type) {
case NormalMessage:
info := v.GetInfo()
user.messageInput <- PortalMessage{info.RemoteJid, user, v, info.Timestamp}
case whatsapp.MessageRevocation:
user.messageInput <- PortalMessage{v.RemoteJid, user, v, 0}
case whatsapp.StreamEvent:
user.HandleStreamEvent(v)
case []whatsapp.Chat:
user.HandleChatList(v)
case []whatsapp.Contact:
user.HandleContactList(v)
case error:
user.HandleError(v)
case whatsapp.Contact:
go user.HandleNewContact(v)
case whatsapp.BatteryMessage:
user.HandleBatteryMessage(v)
case whatsapp.CallInfo:
user.HandleCallInfo(v)
case whatsapp.PresenceEvent:
go user.HandlePresence(v)
case whatsapp.JSONMsgInfo:
go user.HandleMsgInfo(v)
case whatsapp.ReceivedMessage:
user.HandleReceivedMessage(v)
case whatsapp.ReadMessage:
user.HandleReadMessage(v)
case whatsapp.JSONCommand:
user.HandleCommand(v)
case whatsapp.ChatUpdate:
user.HandleChatUpdate(v)
case whatsapp.ConnInfo:
user.HandleConnInfo(v)
case json.RawMessage:
user.HandleJSONMessage(v)
case *waProto.WebMessageInfo:
user.updateLastConnectionIfNecessary()
// TODO trace log
//user.log.Debugfln("WebMessageInfo: %+v", v)
case *waBinary.Node:
user.log.Debugfln("Unknown binary message: %+v", v)
default:
user.log.Debugfln("Unknown type of event in HandleEvent: %T", v)
}
}
func (user *User) HandleStreamEvent(evt whatsapp.StreamEvent) {
if evt.Type == whatsapp.StreamSleep {
if user.lastReconnection+60 > time.Now().Unix() { if user.lastReconnection+60 > time.Now().Unix() {
user.lastReconnection = 0 user.lastReconnection = 0
user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds") user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds")
conn := user.Conn
go func() { go func() {
time.Sleep(20 * time.Second) time.Sleep(20 * time.Second)
// TODO if this happens during the post-login sync, it can get stuck forever // TODO if this happens during the post-login sync, it can get stuck forever
user.postConnPing(conn) // TODO check if the above is still true
user.postConnPing()
}() }()
} }
} else { } else {
@ -580,10 +634,10 @@ func (user *User) HandleChatList(chats []whatsapp.Chat) {
user.log.Infoln("Chat list received") user.log.Infoln("Chat list received")
chatMap := make(map[string]whatsapp.Chat) chatMap := make(map[string]whatsapp.Chat)
for _, chat := range user.Conn.Store.Chats { for _, chat := range user.Conn.Store.Chats {
chatMap[chat.Jid] = chat chatMap[chat.JID] = chat
} }
for _, chat := range chats { for _, chat := range chats {
chatMap[chat.Jid] = chat chatMap[chat.JID] = chat
} }
select { select {
case user.chatListReceived <- struct{}{}: case user.chatListReceived <- struct{}{}:
@ -605,14 +659,14 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
for _, chat := range chatMap { for _, chat := range chatMap {
ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64) ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64)
if err != nil { if err != nil {
user.log.Warnfln("Non-integer last message time in %s: %s", chat.Jid, chat.LastMessageTime) user.log.Warnfln("Non-integer last message time in %s: %s", chat.JID, chat.LastMessageTime)
continue continue
} }
portal := user.GetPortalByJID(chat.Jid) portal := user.GetPortalByJID(chat.JID)
chats = append(chats, Chat{ chats = append(chats, Chat{
Portal: portal, Portal: portal,
Contact: user.Conn.Store.Contacts[chat.Jid], Contact: user.Conn.Store.Contacts[chat.JID],
LastMessageTime: ts, LastMessageTime: ts,
}) })
var inCommunity, ok bool var inCommunity, ok bool
@ -725,22 +779,35 @@ func (user *User) UpdateDirectChats(chats map[id.UserID][]id.RoomID) {
} }
func (user *User) HandleContactList(contacts []whatsapp.Contact) { func (user *User) HandleContactList(contacts []whatsapp.Contact) {
contactMap := make(map[string]whatsapp.Contact) contactMap := make(map[whatsapp.JID]whatsapp.Contact)
for _, contact := range contacts { for _, contact := range contacts {
contactMap[contact.Jid] = contact contactMap[contact.JID] = contact
} }
go user.syncPuppets(contactMap) go user.syncPuppets(contactMap)
} }
func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) { func (user *User) syncPuppets(contacts map[whatsapp.JID]whatsapp.Contact) {
if contacts == nil { if contacts == nil {
contacts = user.Conn.Store.Contacts contacts = user.Conn.Store.Contacts
} }
_, hasSelf := contacts[user.JID]
if !hasSelf {
contacts[user.JID] = whatsapp.Contact{
Name: user.pushName,
Notify: user.pushName,
JID: user.JID,
}
}
user.log.Infoln("Syncing puppet info from contacts") user.log.Infoln("Syncing puppet info from contacts")
for jid, contact := range contacts { for jid, contact := range contacts {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(contact.Jid) puppet := user.bridge.GetPuppetByJID(contact.JID)
puppet.Sync(user, contact) puppet.Sync(user, contact)
} else if strings.HasSuffix(jid, whatsapp.BroadcastSuffix) {
portal := user.GetPortalByJID(contact.JID)
portal.Sync(user, contact)
} }
} }
user.log.Infoln("Finished syncing puppet info from contacts") user.log.Infoln("Finished syncing puppet info from contacts")
@ -796,14 +863,18 @@ func (user *User) tryReconnect(msg string) {
baseDelay = -baseDelay + 1 baseDelay = -baseDelay + 1
} }
delay := baseDelay delay := baseDelay
conn := user.Conn
takeover := false takeover := false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
user.cancelReconnect = cancel
for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts { for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
if user.Conn != conn { select {
user.log.Debugln("Connection was recreated, aborting reconnection attempts") case <-ctx.Done():
user.log.Debugln("tryReconnect context cancelled, aborting reconnection attempts")
return return
default:
} }
err := conn.Restore(takeover) err := user.Conn.Restore(takeover, ctx)
takeover = true takeover = true
if err == nil { if err == nil {
user.ConnectionErrors = 0 user.ConnectionErrors = 0
@ -813,14 +884,23 @@ func (user *User) tryReconnect(msg string) {
user.PostLogin() user.PostLogin()
return return
} else if errors.Is(err, whatsapp.ErrBadRequest) { } else if errors.Is(err, whatsapp.ErrBadRequest) {
user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...") user.log.Warnln("Got init 400 error when trying to reconnect, resetting connection...")
sess, err := conn.Disconnect() err = user.Conn.Disconnect()
if err != nil { if err != nil {
user.log.Debugln("Error while disconnecting for connection reset:", err) user.log.Debugln("Error while disconnecting for connection reset:", err)
} }
user.SetSession(&sess) } else if errors.Is(err, whatsapp.ErrUnpaired) {
} user.log.Errorln("Got init 401 (unpaired) error when trying to reconnect, not retrying")
user.removeFromJIDMap()
//user.JID = ""
user.SetSession(nil)
user.DeleteConnection()
user.sendMarkdownBridgeAlert("\u26a0 Failed to reconnect to WhatsApp: unpaired from phone. " +
"To re-pair your phone, log in again.")
return
} else {
user.log.Errorln("Error while trying to reconnect after disconnection:", err) user.log.Errorln("Error while trying to reconnect after disconnection:", err)
}
tries++ tries++
user.ConnectionErrors++ user.ConnectionErrors++
if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts { if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
@ -841,19 +921,11 @@ func (user *User) tryReconnect(msg string) {
} }
} }
func (user *User) ShouldCallSynchronously() bool { func (user *User) PortalKey(jid whatsapp.JID) database.PortalKey {
return true
}
func (user *User) HandleJSONParseError(err error) {
user.log.Errorln("WhatsApp JSON parse error:", err)
}
func (user *User) PortalKey(jid types.WhatsAppID) database.PortalKey {
return database.NewPortalKey(jid, user.JID) return database.NewPortalKey(jid, user.JID)
} }
func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal { func (user *User) GetPortalByJID(jid whatsapp.JID) *Portal {
return user.bridge.GetPortalByJID(user.PortalKey(jid)) return user.bridge.GetPortalByJID(user.PortalKey(jid))
} }
@ -888,13 +960,16 @@ func (user *User) handleMessageLoop() {
func (user *User) HandleNewContact(contact whatsapp.Contact) { func (user *User) HandleNewContact(contact whatsapp.Contact) {
user.log.Debugfln("Contact message: %+v", contact) user.log.Debugfln("Contact message: %+v", contact)
go func() { if strings.HasSuffix(contact.JID, whatsapp.OldUserSuffix) {
if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) { contact.JID = strings.Replace(contact.JID, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
} }
puppet := user.bridge.GetPuppetByJID(contact.Jid) if strings.HasSuffix(contact.JID, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(contact.JID)
puppet.UpdateName(user, contact) puppet.UpdateName(user, contact)
}() } else if strings.HasSuffix(contact.JID, whatsapp.BroadcastSuffix) {
portal := user.GetPortalByJID(contact.JID)
portal.UpdateName(contact.Name, "", nil, true)
}
} }
func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) { func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
@ -914,53 +989,13 @@ func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
} }
} }
func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleContactMessage(message whatsapp.ContactMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleStubMessage(message whatsapp.StubMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) {
user.messageInput <- PortalMessage{message.RemoteJid, user, message, 0}
}
type FakeMessage struct { type FakeMessage struct {
Text string Text string
ID string ID string
Alert bool Alert bool
} }
func (user *User) HandleCallInfo(info whatsappExt.CallInfo) { func (user *User) HandleCallInfo(info whatsapp.CallInfo) {
if info.Data != nil { if info.Data != nil {
return return
} }
@ -968,19 +1003,19 @@ func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
ID: info.ID, ID: info.ID,
} }
switch info.Type { switch info.Type {
case whatsappExt.CallOffer: case whatsapp.CallOffer:
if !user.bridge.Config.Bridge.CallNotices.Start { if !user.bridge.Config.Bridge.CallNotices.Start {
return return
} }
data.Text = "Incoming call" data.Text = "Incoming call"
data.Alert = true data.Alert = true
case whatsappExt.CallOfferVideo: case whatsapp.CallOfferVideo:
if !user.bridge.Config.Bridge.CallNotices.Start { if !user.bridge.Config.Bridge.CallNotices.Start {
return return
} }
data.Text = "Incoming video call" data.Text = "Incoming video call"
data.Alert = true data.Alert = true
case whatsappExt.CallTerminate: case whatsapp.CallTerminate:
if !user.bridge.Config.Bridge.CallNotices.End { if !user.bridge.Config.Bridge.CallNotices.End {
return return
} }
@ -995,7 +1030,7 @@ func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
} }
} }
func (user *User) HandlePresence(info whatsappExt.Presence) { func (user *User) HandlePresence(info whatsapp.PresenceEvent) {
puppet := user.bridge.GetPuppetByJID(info.SenderJID) puppet := user.bridge.GetPuppetByJID(info.SenderJID)
switch info.Status { switch info.Status {
case whatsapp.PresenceUnavailable: case whatsapp.PresenceUnavailable:
@ -1023,14 +1058,13 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
} }
} }
func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) { func (user *User) HandleMsgInfo(info whatsapp.JSONMsgInfo) {
if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead { if (info.Command == whatsapp.MsgInfoCommandAck || info.Command == whatsapp.MsgInfoCommandAcks) && info.Acknowledgement == whatsapp.AckMessageRead {
portal := user.GetPortalByJID(info.ToJID) portal := user.GetPortalByJID(info.ToJID)
if len(portal.MXID) == 0 { if len(portal.MXID) == 0 {
return return
} }
go func() {
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal) intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
for _, msgID := range info.IDs { for _, msgID := range info.IDs {
msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID) msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID)
@ -1043,13 +1077,12 @@ func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err) user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
} }
} }
}()
} }
} }
func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) { func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
if received.Type == "read" { if received.Type == "read" {
user.markSelfRead(received.Jid, received.Index) go user.markSelfRead(received.Jid, received.Index)
} else { } else {
user.log.Debugfln("Unknown received message type: %+v", received) user.log.Debugfln("Unknown received message type: %+v", received)
} }
@ -1057,12 +1090,12 @@ func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
func (user *User) HandleReadMessage(read whatsapp.ReadMessage) { func (user *User) HandleReadMessage(read whatsapp.ReadMessage) {
user.log.Debugfln("Received chat read message: %+v", read) user.log.Debugfln("Received chat read message: %+v", read)
user.markSelfRead(read.Jid, "") go user.markSelfRead(read.Jid, "")
} }
func (user *User) markSelfRead(jid, messageID string) { func (user *User) markSelfRead(jid, messageID string) {
if strings.HasSuffix(jid, whatsappExt.OldUserSuffix) { if strings.HasSuffix(jid, whatsapp.OldUserSuffix) {
jid = strings.Replace(jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1) jid = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
} }
puppet := user.bridge.GetPuppetByJID(user.JID) puppet := user.bridge.GetPuppetByJID(user.JID)
if puppet == nil { if puppet == nil {
@ -1096,17 +1129,17 @@ func (user *User) markSelfRead(jid, messageID string) {
} }
} }
func (user *User) HandleCommand(cmd whatsappExt.Command) { func (user *User) HandleCommand(cmd whatsapp.JSONCommand) {
switch cmd.Type { switch cmd.Type {
case whatsappExt.CommandPicture: case whatsapp.CommandPicture:
if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) { if strings.HasSuffix(cmd.JID, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(cmd.JID) puppet := user.bridge.GetPuppetByJID(cmd.JID)
go puppet.UpdateAvatar(user, cmd.ProfilePicInfo) go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
} else if user.bridge.Config.Bridge.ChatMetaSync { } else if user.bridge.Config.Bridge.ChatMetaSync {
portal := user.GetPortalByJID(cmd.JID) portal := user.GetPortalByJID(cmd.JID)
go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true) go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
} }
case whatsappExt.CommandDisconnect: case whatsapp.CommandDisconnect:
if cmd.Kind == "replaced" { if cmd.Kind == "replaced" {
user.cleanDisconnection = true user.cleanDisconnection = true
go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" + go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" +
@ -1119,14 +1152,14 @@ func (user *User) HandleCommand(cmd whatsappExt.Command) {
} }
} }
func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) { func (user *User) HandleChatUpdate(cmd whatsapp.ChatUpdate) {
if cmd.Command != whatsappExt.ChatUpdateCommandAction { if cmd.Command != whatsapp.ChatUpdateCommandAction {
return return
} }
portal := user.GetPortalByJID(cmd.JID) portal := user.GetPortalByJID(cmd.JID)
if len(portal.MXID) == 0 { if len(portal.MXID) == 0 {
if cmd.Data.Action == whatsappExt.ChatActionIntroduce || cmd.Data.Action == whatsappExt.ChatActionCreate { if cmd.Data.Action == whatsapp.ChatActionIntroduce || cmd.Data.Action == whatsapp.ChatActionCreate {
go func() { go func() {
err := portal.CreateMatrixRoom(user) err := portal.CreateMatrixRoom(user)
if err != nil { if err != nil {
@ -1139,11 +1172,11 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
// These don't come down the message history :( // These don't come down the message history :(
switch cmd.Data.Action { switch cmd.Data.Action {
case whatsappExt.ChatActionAddTopic: case whatsapp.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil,true) go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionRemoveTopic: case whatsapp.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, nil,true) go portal.UpdateTopic("", cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionRemove: case whatsapp.ChatActionRemove:
// We ignore leaving groups in the message history to avoid accidentally leaving rejoined groups, // We ignore leaving groups in the message history to avoid accidentally leaving rejoined groups,
// but if we get a real-time command that says we left, it should be safe to bridge it. // but if we get a real-time command that says we left, it should be safe to bridge it.
if !user.bridge.Config.Bridge.ChatMetaSync { if !user.bridge.Config.Bridge.ChatMetaSync {
@ -1162,45 +1195,48 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
} }
switch cmd.Data.Action { switch cmd.Data.Action {
case whatsappExt.ChatActionNameChange: case whatsapp.ChatActionNameChange:
go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true) go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionPromote: case whatsapp.ChatActionPromote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true) go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
case whatsappExt.ChatActionDemote: case whatsapp.ChatActionDemote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false) go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
case whatsappExt.ChatActionAnnounce: case whatsapp.ChatActionAnnounce:
go portal.RestrictMessageSending(cmd.Data.Announce) go portal.RestrictMessageSending(cmd.Data.Announce)
case whatsappExt.ChatActionRestrict: case whatsapp.ChatActionRestrict:
go portal.RestrictMetadataChanges(cmd.Data.Restrict) go portal.RestrictMetadataChanges(cmd.Data.Restrict)
case whatsappExt.ChatActionRemove: case whatsapp.ChatActionRemove:
go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs) go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionAdd: case whatsapp.ChatActionAdd:
go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs) go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionIntroduce: case whatsapp.ChatActionIntroduce:
if cmd.Data.SenderJID != "unknown" { if cmd.Data.SenderJID != "unknown" {
go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID}) go portal.Sync(user, whatsapp.Contact{JID: portal.Key.JID})
} }
} }
} }
func (user *User) HandleJsonMessage(message string) { func (user *User) HandleConnInfo(info whatsapp.ConnInfo) {
var msg json.RawMessage if user.Session != nil && info.Connected && len(info.ClientToken) > 0 {
err := json.Unmarshal([]byte(message), &msg) user.log.Debugln("Received new tokens")
if err != nil { user.Session.ClientToken = info.ClientToken
user.Session.ServerToken = info.ServerToken
user.Session.Wid = info.WID
user.Update()
}
if len(info.PushName) > 0 {
user.pushName = info.PushName
}
}
func (user *User) HandleJSONMessage(message json.RawMessage) {
if !json.Valid(message) {
return return
} }
user.log.Debugln("JSON message:", message) user.log.Debugfln("JSON message: %s", message)
user.updateLastConnectionIfNecessary() user.updateLastConnectionIfNecessary()
} }
func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
user.updateLastConnectionIfNecessary()
}
func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) {
user.log.Debugfln("Unknown binary message: %+v", node)
}
func (user *User) NeedsRelaybot(portal *Portal) bool { func (user *User) NeedsRelaybot(portal *Portal) bool {
return !user.HasSession() || !user.IsInPortal(portal.Key) return !user.HasSession() || !user.IsInPortal(portal.Key)
} }

View file

@ -1,72 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type CallInfoType string
const (
CallOffer CallInfoType = "offer"
CallOfferVideo CallInfoType = "offer_video"
CallTransport CallInfoType = "transport"
CallRelayLatency CallInfoType = "relaylatency"
CallTerminate CallInfoType = "terminate"
)
type CallInfo struct {
ID string `json:"id"`
Type CallInfoType `json:"type"`
From string `json:"from"`
Platform string `json:"platform"`
Version []int `json:"version"`
Data [][]interface{} `json:"data"`
}
type CallInfoHandler interface {
whatsapp.Handler
HandleCallInfo(CallInfo)
}
func (ext *ExtendedConn) handleMessageCall(message []byte) {
var event CallInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.From = strings.Replace(event.From, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
callInfoHandler, ok := handler.(CallInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(callInfoHandler) {
callInfoHandler.HandleCallInfo(event)
} else {
go callInfoHandler.HandleCallInfo(event)
}
}
}

View file

@ -1,183 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type ChatUpdateCommand string
const (
ChatUpdateCommandAction ChatUpdateCommand = "action"
)
type ChatUpdate struct {
JID string `json:"id"`
Command ChatUpdateCommand `json:"cmd"`
Data ChatUpdateData `json:"data"`
}
type ChatActionType string
const (
ChatActionNameChange ChatActionType = "subject"
ChatActionAddTopic ChatActionType = "desc_add"
ChatActionRemoveTopic ChatActionType = "desc_remove"
ChatActionRestrict ChatActionType = "restrict"
ChatActionAnnounce ChatActionType = "announce"
ChatActionPromote ChatActionType = "promote"
ChatActionDemote ChatActionType = "demote"
ChatActionIntroduce ChatActionType = "introduce"
ChatActionCreate ChatActionType = "create"
ChatActionRemove ChatActionType = "remove"
ChatActionAdd ChatActionType = "add"
)
type ChatUpdateData struct {
Action ChatActionType
SenderJID string
NameChange struct {
Name string `json:"subject"`
SetAt int64 `json:"s_t"`
SetBy string `json:"s_o"`
}
AddTopic struct {
Topic string `json:"desc"`
ID string `json:"descId"`
SetAt int64 `json:"descTime"`
SetBy string `json:"descOwner"`
}
RemoveTopic struct {
ID string `json:"descId"`
}
Introduce struct {
CreationTime int64 `json:"creation"`
Admins []string `json:"admins"`
SuperAdmins []string `json:"superadmins"`
Regulars []string `json:"regulars"`
}
Restrict bool
Announce bool
UserChange struct {
JIDs []string `json:"participants"`
}
}
func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error {
var arr []json.RawMessage
err := json.Unmarshal(data, &arr)
if err != nil {
return err
} else if len(arr) < 3 {
return nil
}
err = json.Unmarshal(arr[0], &cud.Action)
if err != nil {
return err
}
err = json.Unmarshal(arr[1], &cud.SenderJID)
if err != nil {
return err
}
cud.SenderJID = strings.Replace(cud.SenderJID, OldUserSuffix, NewUserSuffix, 1)
var unmarshalTo interface{}
switch cud.Action {
case ChatActionIntroduce, ChatActionCreate:
err = json.Unmarshal(arr[2], &cud.NameChange)
if err != nil {
return err
}
err = json.Unmarshal(arr[2], &cud.AddTopic)
if err != nil {
return err
}
unmarshalTo = &cud.Introduce
case ChatActionNameChange:
unmarshalTo = &cud.NameChange
case ChatActionAddTopic:
unmarshalTo = &cud.AddTopic
case ChatActionRemoveTopic:
unmarshalTo = &cud.RemoveTopic
case ChatActionRestrict:
unmarshalTo = &cud.Restrict
case ChatActionAnnounce:
unmarshalTo = &cud.Announce
case ChatActionPromote, ChatActionDemote, ChatActionRemove, ChatActionAdd:
unmarshalTo = &cud.UserChange
default:
return nil
}
err = json.Unmarshal(arr[2], unmarshalTo)
if err != nil {
return err
}
cud.NameChange.SetBy = strings.Replace(cud.NameChange.SetBy, OldUserSuffix, NewUserSuffix, 1)
for index, jid := range cud.UserChange.JIDs {
cud.UserChange.JIDs[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.SuperAdmins {
cud.Introduce.SuperAdmins[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.Admins {
cud.Introduce.Admins[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
for index, jid := range cud.Introduce.Regulars {
cud.Introduce.Regulars[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
}
return nil
}
type ChatUpdateHandler interface {
whatsapp.Handler
HandleChatUpdate(ChatUpdate)
}
func (ext *ExtendedConn) handleMessageChatUpdate(message []byte) {
var event ChatUpdate
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
chatUpdateHandler, ok := handler.(ChatUpdateHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(chatUpdateHandler) {
chatUpdateHandler.HandleChatUpdate(event)
} else {
go chatUpdateHandler.HandleChatUpdate(event)
}
}
}

View file

@ -1,69 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type CommandType string
const (
CommandPicture CommandType = "picture"
CommandDisconnect CommandType = "disconnect"
)
type Command struct {
Type CommandType `json:"type"`
JID string `json:"jid"`
*ProfilePicInfo
Kind string `json:"kind"`
Raw json.RawMessage `json:"-"`
}
type CommandHandler interface {
whatsapp.Handler
HandleCommand(Command)
}
func (ext *ExtendedConn) handleMessageCommand(message []byte) {
var event Command
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.Raw = message
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
for _, handler := range ext.handlers {
commandHandler, ok := handler.(CommandHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(commandHandler) {
commandHandler.HandleCommand(event)
} else {
go commandHandler.HandleCommand(event)
}
}
}

View file

@ -1,65 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type ConnInfo struct {
ProtocolVersion []int `json:"protoVersion"`
BinaryVersion int `json:"binVersion"`
Phone struct {
WhatsAppVersion string `json:"wa_version"`
MCC string `json:"mcc"`
MNC string `json:"mnc"`
OSVersion string `json:"os_version"`
DeviceManufacturer string `json:"device_manufacturer"`
DeviceModel string `json:"device_model"`
OSBuildNumber string `json:"os_build_number"`
} `json:"phone"`
Features map[string]interface{} `json:"features"`
PushName string `json:"pushname"`
}
type ConnInfoHandler interface {
whatsapp.Handler
HandleConnInfo(ConnInfo)
}
func (ext *ExtendedConn) handleMessageConn(message []byte) {
var event ConnInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
for _, handler := range ext.handlers {
connInfoHandler, ok := handler.(ConnInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(connInfoHandler) {
connInfoHandler.HandleConnInfo(event)
} else {
go connInfoHandler.HandleConnInfo(event)
}
}
}

View file

@ -1,68 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"fmt"
"maunium.net/go/mautrix-whatsapp/types"
)
type CreateGroupResponse struct {
Status int `json:"status"`
GroupID types.WhatsAppID `json:"gid"`
Participants map[types.WhatsAppID]struct {
Code string `json:"code"`
} `json:"participants"`
Source string `json:"-"`
}
type actualCreateGroupResponse struct {
Status int `json:"status"`
GroupID types.WhatsAppID `json:"gid"`
Participants []map[types.WhatsAppID]struct {
Code string `json:"code"`
} `json:"participants"`
}
func (ext *ExtendedConn) CreateGroup(subject string, participants []types.WhatsAppID) (*CreateGroupResponse, error) {
respChan, err := ext.Conn.CreateGroup(subject, participants)
if err != nil {
return nil, err
}
var resp CreateGroupResponse
var actualResp actualCreateGroupResponse
resp.Source = <-respChan
fmt.Println(">>>>>>", resp.Source)
err = json.Unmarshal([]byte(resp.Source), &actualResp)
if err != nil {
return nil, err
}
resp.Status = actualResp.Status
resp.GroupID = actualResp.GroupID
resp.Participants = make(map[types.WhatsAppID]struct {
Code string `json:"code"`
})
for _, participantMap := range actualResp.Participants {
for jid, status := range participantMap {
resp.Participants[jid] = status
}
}
return &resp, nil
}

View file

@ -1,105 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type JSONMessage []json.RawMessage
type JSONMessageType string
const (
MessageMsgInfo JSONMessageType = "MsgInfo"
MessageMsg JSONMessageType = "Msg"
MessagePresence JSONMessageType = "Presence"
MessageStream JSONMessageType = "Stream"
MessageConn JSONMessageType = "Conn"
MessageProps JSONMessageType = "Props"
MessageCmd JSONMessageType = "Cmd"
MessageChat JSONMessageType = "Chat"
MessageCall JSONMessageType = "Call"
)
func (ext *ExtendedConn) HandleError(error) {}
type UnhandledJSONMessageHandler interface {
whatsapp.Handler
HandleUnhandledJSONMessage(string)
}
type JSONParseErrorHandler interface {
whatsapp.Handler
HandleJSONParseError(error)
}
func (ext *ExtendedConn) jsonParseError(err error) {
for _, handler := range ext.handlers {
errorHandler, ok := handler.(JSONParseErrorHandler)
if !ok {
continue
}
errorHandler.HandleJSONParseError(err)
}
}
func (ext *ExtendedConn) HandleJsonMessage(message string) {
msg := JSONMessage{}
err := json.Unmarshal([]byte(message), &msg)
if err != nil || len(msg) < 2 {
ext.jsonParseError(err)
return
}
var msgType JSONMessageType
json.Unmarshal(msg[0], &msgType)
switch msgType {
case MessagePresence:
ext.handleMessagePresence(msg[1])
case MessageStream:
ext.handleMessageStream(msg[1:])
case MessageConn:
ext.handleMessageConn(msg[1])
case MessageProps:
ext.handleMessageProps(msg[1])
case MessageMsgInfo, MessageMsg:
ext.handleMessageMsgInfo(msgType, msg[1])
case MessageCmd:
ext.handleMessageCommand(msg[1])
case MessageChat:
ext.handleMessageChatUpdate(msg[1])
case MessageCall:
ext.handleMessageCall(msg[1])
default:
for _, handler := range ext.handlers {
ujmHandler, ok := handler.(UnhandledJSONMessageHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(ujmHandler) {
ujmHandler.HandleUnhandledJSONMessage(message)
} else {
go ujmHandler.HandleUnhandledJSONMessage(message)
}
}
}
}

View file

@ -1,95 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type MsgInfoCommand string
const (
MsgInfoCommandAck MsgInfoCommand = "ack"
MsgInfoCommandAcks MsgInfoCommand = "acks"
)
type Acknowledgement int
const (
AckMessageSent Acknowledgement = 1
AckMessageDelivered Acknowledgement = 2
AckMessageRead Acknowledgement = 3
)
type JSONStringOrArray []string
func (jsoa *JSONStringOrArray) UnmarshalJSON(data []byte) error {
var str string
if json.Unmarshal(data, &str) == nil {
*jsoa = []string{str}
return nil
}
var strs []string
json.Unmarshal(data, &strs)
*jsoa = strs
return nil
}
type MsgInfo struct {
Command MsgInfoCommand `json:"cmd"`
IDs JSONStringOrArray `json:"id"`
Acknowledgement Acknowledgement `json:"ack"`
MessageFromJID string `json:"from"`
SenderJID string `json:"participant"`
ToJID string `json:"to"`
Timestamp int64 `json:"t"`
}
type MsgInfoHandler interface {
whatsapp.Handler
HandleMsgInfo(MsgInfo)
}
func (ext *ExtendedConn) handleMessageMsgInfo(msgType JSONMessageType, message []byte) {
var event MsgInfo
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.MessageFromJID = strings.Replace(event.MessageFromJID, OldUserSuffix, NewUserSuffix, 1)
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
event.ToJID = strings.Replace(event.ToJID, OldUserSuffix, NewUserSuffix, 1)
if msgType == MessageMsg {
event.SenderJID = event.ToJID
}
for _, handler := range ext.handlers {
msgInfoHandler, ok := handler.(MsgInfoHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(msgInfoHandler) {
msgInfoHandler.HandleMsgInfo(event)
} else {
go msgInfoHandler.HandleMsgInfo(event)
}
}
}

View file

@ -1,64 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"strings"
"github.com/Rhymen/go-whatsapp"
)
type Presence struct {
JID string `json:"id"`
SenderJID string `json:"participant"`
Status whatsapp.Presence `json:"type"`
Timestamp int64 `json:"t"`
Deny bool `json:"deny"`
}
type PresenceHandler interface {
whatsapp.Handler
HandlePresence(Presence)
}
func (ext *ExtendedConn) handleMessagePresence(message []byte) {
var event Presence
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
if len(event.SenderJID) == 0 {
event.SenderJID = event.JID
} else {
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
}
for _, handler := range ext.handlers {
presenceHandler, ok := handler.(PresenceHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(presenceHandler) {
presenceHandler.HandlePresence(event)
} else {
go presenceHandler.HandlePresence(event)
}
}
}

View file

@ -1,73 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type ProtocolProps struct {
WebPresence bool `json:"webPresence"`
NotificationQuery bool `json:"notificationQuery"`
FacebookCrashLog bool `json:"fbCrashlog"`
Bucket string `json:"bucket"`
GIFSearch string `json:"gifSearch"`
Spam bool `json:"SPAM"`
SetBlock bool `json:"SET_BLOCK"`
MessageInfo bool `json:"MESSAGE_INFO"`
MaxFileSize int `json:"maxFileSize"`
Media int `json:"media"`
GroupNameLength int `json:"maxSubject"`
GroupDescriptionLength int `json:"groupDescLength"`
MaxParticipants int `json:"maxParticipants"`
VideoMaxEdge int `json:"videoMaxEdge"`
ImageMaxEdge int `json:"imageMaxEdge"`
ImageMaxKilobytes int `json:"imageMaxKBytes"`
Edit int `json:"edit"`
FwdUIStartTimestamp int `json:"fwdUiStartTs"`
GroupsV3 int `json:"groupsV3"`
RestrictGroups int `json:"restrictGroups"`
AnnounceGroups int `json:"announceGroups"`
}
type ProtocolPropsHandler interface {
whatsapp.Handler
HandleProtocolProps(ProtocolProps)
}
func (ext *ExtendedConn) handleMessageProps(message []byte) {
var event ProtocolProps
err := json.Unmarshal(message, &event)
if err != nil {
ext.jsonParseError(err)
return
}
for _, handler := range ext.handlers {
protocolPropsHandler, ok := handler.(ProtocolPropsHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(protocolPropsHandler) {
protocolPropsHandler.HandleProtocolProps(event)
} else {
go protocolPropsHandler.HandleProtocolProps(event)
}
}
}

View file

@ -1,59 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"github.com/Rhymen/go-whatsapp"
"github.com/Rhymen/go-whatsapp/binary/proto"
)
type MessageRevokeHandler interface {
whatsapp.Handler
HandleMessageRevoke(key MessageRevocation)
}
type MessageRevocation struct {
Id string
RemoteJid string
FromMe bool
Participant string
}
func (ext *ExtendedConn) HandleRawMessage(message *proto.WebMessageInfo) {
protoMsg := message.GetMessage().GetProtocolMessage()
if protoMsg != nil && protoMsg.GetType() == proto.ProtocolMessage_REVOKE {
key := protoMsg.GetKey()
deletedMessage := MessageRevocation{
Id: key.GetId(),
RemoteJid: key.GetRemoteJid(),
FromMe: key.GetFromMe(),
Participant: key.GetParticipant(),
}
for _, handler := range ext.handlers {
mrHandler, ok := handler.(MessageRevokeHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(mrHandler) {
mrHandler.HandleMessageRevoke(deletedMessage)
} else {
go mrHandler.HandleMessageRevoke(deletedMessage)
}
}
}
}

View file

@ -1,76 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"github.com/Rhymen/go-whatsapp"
)
type StreamType string
const (
StreamUpdate = "update"
StreamSleep = "asleep"
)
type StreamEvent struct {
Type StreamType
IsOutdated bool
Version string
Extra []json.RawMessage
}
type StreamEventHandler interface {
whatsapp.Handler
HandleStreamEvent(StreamEvent)
}
func (ext *ExtendedConn) handleMessageStream(message []json.RawMessage) {
var event StreamEvent
err := json.Unmarshal(message[0], &event.Type)
if err != nil {
ext.jsonParseError(err)
return
}
if event.Type == StreamUpdate && len(message) >= 3 {
_ = json.Unmarshal(message[1], &event.IsOutdated)
_ = json.Unmarshal(message[2], &event.Version)
if len(message) >= 4 {
event.Extra = message[3:]
}
} else if len(message) >= 2 {
event.Extra = message[1:]
}
for _, handler := range ext.handlers {
streamHandler, ok := handler.(StreamEventHandler)
if !ok {
continue
}
if ext.shouldCallSynchronously(streamHandler) {
streamHandler.HandleStreamEvent(event)
} else {
go streamHandler.HandleStreamEvent(event)
}
}
}

View file

@ -1,164 +0,0 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package whatsappExt
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/Rhymen/go-whatsapp"
)
const (
OldUserSuffix = "@c.us"
NewUserSuffix = "@s.whatsapp.net"
)
type ExtendedConn struct {
*whatsapp.Conn
handlers []whatsapp.Handler
}
func ExtendConn(conn *whatsapp.Conn) *ExtendedConn {
ext := &ExtendedConn{
Conn: conn,
}
ext.Conn.AddHandler(ext)
return ext
}
func (ext *ExtendedConn) AddHandler(handler whatsapp.Handler) {
ext.Conn.AddHandler(handler)
ext.handlers = append(ext.handlers, handler)
}
func (ext *ExtendedConn) RemoveHandler(handler whatsapp.Handler) bool {
ext.Conn.RemoveHandler(handler)
for i, v := range ext.handlers {
if v == handler {
ext.handlers = append(ext.handlers[:i], ext.handlers[i+1:]...)
return true
}
}
return false
}
func (ext *ExtendedConn) RemoveHandlers() {
ext.Conn.RemoveHandlers()
ext.handlers = make([]whatsapp.Handler, 0)
}
func (ext *ExtendedConn) shouldCallSynchronously(handler whatsapp.Handler) bool {
sh, ok := handler.(whatsapp.SyncHandler)
return ok && sh.ShouldCallSynchronously()
}
func (ext *ExtendedConn) ShouldCallSynchronously() bool {
return true
}
type GroupInfo struct {
JID string `json:"jid"`
OwnerJID string `json:"owner"`
Name string `json:"subject"`
NameSetTime int64 `json:"subjectTime"`
NameSetBy string `json:"subjectOwner"`
Announce bool `json:"announce"` // Can only admins send messages?
Topic string `json:"desc"`
TopicID string `json:"descId"`
TopicSetAt int64 `json:"descTime"`
TopicSetBy string `json:"descOwner"`
GroupCreated int64 `json:"creation"`
Status int16 `json:"status"`
Participants []struct {
JID string `json:"id"`
IsAdmin bool `json:"isAdmin"`
IsSuperAdmin bool `json:"isSuperAdmin"`
} `json:"participants"`
}
func (ext *ExtendedConn) GetGroupMetaData(jid string) (*GroupInfo, error) {
data, err := ext.Conn.GetGroupMetaData(jid)
if err != nil {
return nil, fmt.Errorf("failed to get group metadata: %v", err)
}
content := <-data
info := &GroupInfo{}
err = json.Unmarshal([]byte(content), info)
if err != nil {
return info, fmt.Errorf("failed to unmarshal group metadata: %v", err)
}
for index, participant := range info.Participants {
info.Participants[index].JID = strings.Replace(participant.JID, OldUserSuffix, NewUserSuffix, 1)
}
info.NameSetBy = strings.Replace(info.NameSetBy, OldUserSuffix, NewUserSuffix, 1)
info.TopicSetBy = strings.Replace(info.TopicSetBy, OldUserSuffix, NewUserSuffix, 1)
return info, nil
}
type ProfilePicInfo struct {
URL string `json:"eurl"`
Tag string `json:"tag"`
Status int `json:"status"`
}
func (ppi *ProfilePicInfo) Download() (io.ReadCloser, error) {
resp, err := http.Get(ppi.URL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
func (ppi *ProfilePicInfo) DownloadBytes() ([]byte, error) {
body, err := ppi.Download()
if err != nil {
return nil, err
}
defer body.Close()
data, err := ioutil.ReadAll(body)
return data, err
}
func (ext *ExtendedConn) GetProfilePicThumb(jid string) (*ProfilePicInfo, error) {
data, err := ext.Conn.GetProfilePicThumb(jid)
if err != nil {
return nil, fmt.Errorf("failed to get avatar: %v", err)
}
content := <-data
info := &ProfilePicInfo{}
err = json.Unmarshal([]byte(content), info)
if err != nil {
return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
}
return info, nil
}