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
import (
"context"
"errors"
"fmt"
"math"
@ -35,7 +36,6 @@ import (
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
)
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)
portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(jid))
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)
} else {
err = portal.CreateMatrixRoom(ce.User)
@ -412,11 +412,11 @@ func (handler *CommandHandler) CommandLogout(ce *CommandEvent) {
ce.Reply("Unknown error while logging out: %v", err)
return
}
ce.User.Disconnect()
ce.User.removeFromJIDMap()
// TODO this causes a foreign key violation, which should be fixed
//ce.User.JID = ""
ce.User.SetSession(nil)
ce.User.DeleteConnection()
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.")
return
}
ce.User.Disconnect()
//ce.User.JID = ""
ce.User.removeFromJIDMap()
ce.User.SetSession(nil)
ce.User.DeleteConnection()
ce.Reply("Session information purged")
}
@ -490,24 +491,21 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
}
wasConnected := true
sess, err := ce.User.Conn.Disconnect()
err := ce.User.Conn.Disconnect()
if err == whatsapp.ErrNotConnected {
wasConnected = false
} else if err != nil {
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 ce.User.Session != nil {
ce.User.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
var sess whatsapp.Session
sess, err = ce.User.Conn.RestoreWithSession(*ce.User.Session)
if err == nil {
ce.User.SetSession(&sess)
}
ce.User.Conn.SetSession(*ce.User.Session)
err = ce.User.Conn.Restore(true, ctx)
} else {
ce.Reply("You are not logged in.")
return
@ -521,17 +519,11 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
}
if err != nil {
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.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 {
ce.User.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
} else {
ce.User.SetSession(&sess)
}
return
}
@ -554,7 +546,7 @@ func (handler *CommandHandler) CommandDeleteConnection(ce *CommandEvent) {
ce.Reply("You don't have a WhatsApp connection.")
return
}
ce.User.Disconnect()
ce.User.DeleteConnection()
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.")
return
}
sess, err := ce.User.Conn.Disconnect()
err := ce.User.Conn.Disconnect()
if err == whatsapp.ErrNotConnected {
ce.Reply("You were not connected.")
return
@ -573,8 +565,6 @@ func (handler *CommandHandler) CommandDisconnect(ce *CommandEvent) {
ce.User.log.Warnln("Error while disconnecting:", err)
ce.Reply("Unknown error while disconnecting: %v", err)
return
} else {
ce.User.SetSession(&sess)
}
ce.User.bridge.Metrics.TrackConnectionState(ce.User.JID, false)
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) {
for jid, contact := range input {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) != contacts {
if strings.HasSuffix(jid, whatsapp.NewUserSuffix) != contacts {
continue
}
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 {
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))
@ -818,8 +808,8 @@ func (handler *CommandHandler) CommandOpen(ce *CommandEvent) {
user := ce.User
jid := ce.Args[0]
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) {
ce.Reply("That looks like a user JID. Did you mean `pm %s`?", jid[:len(jid)-len(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(whatsapp.NewUserSuffix)])
return
}
@ -865,7 +855,7 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
return
}
}
jid := number + whatsappExt.NewUserSuffix
jid := number + whatsapp.NewUserSuffix
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>`.")
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)
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 {
err := portal.MainIntent().EnsureInvited(portal.MXID, user.MXID)
if err != nil {

View file

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

View file

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

View file

@ -22,25 +22,25 @@ import (
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 {
JID types.WhatsAppID
Receiver types.WhatsAppID
JID whatsapp.JID
Receiver whatsapp.JID
}
func GroupPortalKey(jid types.WhatsAppID) PortalKey {
func GroupPortalKey(jid whatsapp.JID) PortalKey {
return PortalKey{
JID: jid,
Receiver: jid,
}
}
func NewPortalKey(jid, receiver types.WhatsAppID) PortalKey {
if strings.HasSuffix(jid, "@g.us") {
func NewPortalKey(jid, receiver whatsapp.JID) PortalKey {
if strings.HasSuffix(jid, whatsapp.GroupSuffix) {
receiver = jid
}
return PortalKey{
@ -80,11 +80,11 @@ func (pq *PortalQuery) GetByMXID(mxid id.RoomID) *Portal {
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)
}
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)
}

View file

@ -21,9 +21,9 @@ import (
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 {
@ -53,7 +53,7 @@ func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
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)
if row == nil {
return nil
@ -85,7 +85,7 @@ type Puppet struct {
db *Database
log log.Logger
JID types.WhatsAppID
JID whatsapp.JID
Avatar string
AvatarURL id.ContentURI
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
}
const NumberOfUpgrades = 20
const NumberOfUpgrades = 21
var upgrades [NumberOfUpgrades]upgrade

View file

@ -26,8 +26,6 @@ import (
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
"maunium.net/go/mautrix/id"
)
@ -63,7 +61,7 @@ func (uq *UserQuery) GetByMXID(userID id.UserID) *User {
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))
if row == nil {
return nil
@ -76,7 +74,7 @@ type User struct {
log log.Logger
MXID id.UserID
JID types.WhatsAppID
JID whatsapp.JID
ManagementRoom id.RoomID
Session *whatsapp.Session
LastConnection uint64
@ -93,14 +91,14 @@ func (user *User) Scan(row Scannable) *User {
return nil
}
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{
ClientId: clientID.String,
ClientID: clientID.String,
ClientToken: clientToken.String,
ServerToken: serverToken.String,
EncKey: encKey,
MacKey: macKey,
Wid: jid.String + whatsappExt.OldUserSuffix,
Wid: jid.String + whatsapp.OldUserSuffix,
}
} else {
user.Session = nil
@ -108,7 +106,7 @@ func (user *User) Scan(row Scannable) *User {
return user
}
func stripSuffix(jid types.WhatsAppID) string {
func stripSuffix(jid whatsapp.JID) string {
if len(jid) == 0 {
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)`,
user.MXID, 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)
if err != nil {
user.log.Warnfln("Failed to insert %s: %v", user.MXID, err)
}
@ -160,7 +158,7 @@ func (user *User) Update() {
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`,
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)
if err != nil {
user.log.Warnfln("Failed to update %s: %v", user.MXID, err)

View file

@ -22,12 +22,11 @@ import (
"regexp"
"strings"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"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]|$)")
@ -58,9 +57,9 @@ func NewFormatter(bridge *Bridge) *Formatter {
if mxid[0] == '@' {
puppet := bridge.GetPuppetByMXID(id.UserID(mxid))
if puppet != nil {
jids, ok := ctx[mentionedJIDsContextKey].([]types.WhatsAppID)
jids, ok := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
if !ok {
ctx[mentionedJIDsContextKey] = []types.WhatsAppID{puppet.JID}
ctx[mentionedJIDsContextKey] = []whatsapp.JID{puppet.JID}
} else {
ctx[mentionedJIDsContextKey] = append(jids, puppet.JID)
}
@ -105,7 +104,7 @@ func NewFormatter(bridge *Bridge) *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 {
mxid = user.MXID
displayname = string(user.MXID)
@ -116,7 +115,7 @@ func (formatter *Formatter) getMatrixInfoByJID(jid types.WhatsAppID) (mxid id.Us
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)
for regex, replacement := range formatter.waReplString {
output = regex.ReplaceAllString(output, replacement)
@ -126,7 +125,7 @@ func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, me
}
for _, jid := range mentionedJIDs {
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)
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)
result := formatter.matrixHTMLParser.Parse(html, ctx)
mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]types.WhatsAppID)
mentionedJIDs, _ := ctx[mentionedJIDsContextKey].([]whatsapp.JID)
return result, mentionedJIDs
}

12
go.mod
View file

@ -5,15 +5,15 @@ go 1.14
require (
github.com/Rhymen/go-whatsapp v0.1.0
github.com/gorilla/websocket v1.4.2
github.com/lib/pq v1.7.0
github.com/mattn/go-sqlite3 v1.14.0
github.com/prometheus/client_golang v1.7.0
github.com/lib/pq v1.9.0
github.com/mattn/go-sqlite3 v1.14.6
github.com/prometheus/client_golang v1.9.0
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
gopkg.in/yaml.v2 v2.3.0
maunium.net/go/mauflag v1.0.0
maunium.net/go/maulogger/v2 v2.1.1
maunium.net/go/mautrix v0.8.2
maunium.net/go/maulogger/v2 v2.2.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.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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/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-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-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 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/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/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=
@ -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/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/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/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/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/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 v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/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/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/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.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.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/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.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/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/protobuf v1.2.0/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/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
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.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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/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/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/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/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/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.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/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
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/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-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 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-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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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.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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-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.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
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-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-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/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.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.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-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
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/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/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/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.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/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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
github.com/tulir/go-whatsapp v0.3.13 h1:RPc/GdZ7KlhlGiZp2Zk7B/OP9v0l7ywOt5I2kKAZ+xU=
github.com/tulir/go-whatsapp v0.3.13/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.14 h1:VKXBMw6GvGKRQTceEQ9Dcg6wR+jRy5/G8SDPbisWZqs=
github.com/tulir/go-whatsapp v0.3.14/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.15 h1:Ogu+f5hvB6Fbdjl6BG7nUb5wuJGCzUa9Z1FraS23M50=
github.com/tulir/go-whatsapp v0.3.15/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.16 h1:NfcXC2DQXwls3qkAjbFqSeoMX+rUbbpBBGGvCXI3RUw=
github.com/tulir/go-whatsapp v0.3.16/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.17 h1:HMRT6HzP1seUt5P0waD8CxThB2bfBgKX2uVjOoXCaf8=
github.com/tulir/go-whatsapp v0.3.17/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.18 h1:45pkdjEnAp6yV4RTSWCZn2Eenep+MN7Kndf/rRXQtyY=
github.com/tulir/go-whatsapp v0.3.18/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.19 h1:76VtmcjKGX8MbfJN9NNi1f0IVmigTLUcxqE1VRcovcQ=
github.com/tulir/go-whatsapp v0.3.19/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.20 h1:nK92MgruqXwk+QlaAS39xhzHNbFvJIEgUIOUrN3i8Yc=
github.com/tulir/go-whatsapp v0.3.20/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.21 h1:2m7gUw4oHX4kIpMmP9VwCR7KEUK/PHhXLygPFGF9XfI=
github.com/tulir/go-whatsapp v0.3.21/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tulir/go-whatsapp v0.4.0-rc.1 h1:pYaw/V1gzXmchO3wcjI3+LJvNAxsiw2EysG5dUzufCI=
github.com/tulir/go-whatsapp v0.4.0-rc.1/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
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-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-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-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-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-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-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-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-20200202094626-16171245cfb2/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-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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-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-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-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-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-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-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-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-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-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=
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.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/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-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.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.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/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=
@ -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=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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/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/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.2/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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-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/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.1.1 h1:NAZNc6XUFJzgzfewCzVoGkxNAsblLCSSEdtDuIjP0XA=
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.8.0-rc.3 h1:bb18oNxHUmeiJ0V63YTRVGMjgoeLwu+G40l4n42Z5GI=
maunium.net/go/mautrix v0.8.0-rc.3/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
maunium.net/go/mautrix v0.8.0-rc.4 h1:3JXoL2JJPE5nh/YSw9sv9dQA9ulma9yHTMOBMBY1xdo=
maunium.net/go/mautrix v0.8.0-rc.4/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
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=
maunium.net/go/maulogger/v2 v2.2.2 h1:NCw+7Be1GQFm8xXJ4M2C0Q8yLBTx3c5s7UZ4y1anZMU=
maunium.net/go/maulogger/v2 v2.2.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.8.3 h1:nKGdARVCf2w7thEN5GEbAjYrlLjKLX44jOdB1h+BV7U=
maunium.net/go/mautrix v0.8.3/go.mod h1:LPbb/DeAmtOPKnGbJazL9g11cO3mMAaEbLE8udd98BU=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

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"
"time"
"github.com/Rhymen/go-whatsapp"
flag "maunium.net/go/mauflag"
log "maunium.net/go/maulogger/v2"
@ -36,7 +38,6 @@ import (
"maunium.net/go/mautrix-whatsapp/config"
"maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/database/upgrades"
"maunium.net/go/mautrix-whatsapp/types"
)
var (
@ -137,14 +138,14 @@ type Bridge struct {
Metrics *MetricsHandler
usersByMXID map[id.UserID]*User
usersByJID map[types.WhatsAppID]*User
usersByJID map[whatsapp.JID]*User
usersLock sync.Mutex
managementRooms map[id.RoomID]*User
managementRoomsLock sync.Mutex
portalsByMXID map[id.RoomID]*Portal
portalsByJID map[database.PortalKey]*Portal
portalsLock sync.Mutex
puppets map[types.WhatsAppID]*Puppet
puppets map[whatsapp.JID]*Puppet
puppetsByCustomMXID map[id.UserID]*Puppet
puppetsLock sync.Mutex
}
@ -163,11 +164,11 @@ type Crypto interface {
func NewBridge() *Bridge {
bridge := &Bridge{
usersByMXID: make(map[id.UserID]*User),
usersByJID: make(map[types.WhatsAppID]*User),
usersByJID: make(map[whatsapp.JID]*User),
managementRooms: make(map[id.RoomID]*User),
portalsByMXID: make(map[id.RoomID]*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),
}
@ -383,11 +384,9 @@ func (bridge *Bridge) Stop() {
continue
}
bridge.Log.Debugln("Disconnecting", user.MXID)
sess, err := user.Conn.Disconnect()
err := user.Conn.Disconnect()
if err != nil {
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
}
if mx.shouldIgnoreEvent(evt) {
return
}
user := mx.bridge.GetUserByMXID(evt.Sender)
if user == nil || !user.Whitelisted || !user.IsConnected() {
return

View file

@ -27,11 +27,12 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
)
type MetricsHandler struct {
@ -56,11 +57,11 @@ type MetricsHandler struct {
unencryptedPrivateCount prometheus.Gauge
connected prometheus.Gauge
connectedState map[types.WhatsAppID]bool
connectedState map[whatsapp.JID]bool
loggedIn prometheus.Gauge
loggedInState map[types.WhatsAppID]bool
loggedInState map[whatsapp.JID]bool
syncLocked prometheus.Gauge
syncLockedState map[types.WhatsAppID]bool
syncLockedState map[whatsapp.JID]bool
bufferLength *prometheus.GaugeVec
}
@ -109,17 +110,17 @@ func NewMetricsHandler(address string, log log.Logger, db *database.Database) *M
Name: "bridge_logged_in",
Help: "Users logged into the bridge",
}),
loggedInState: make(map[types.WhatsAppID]bool),
loggedInState: make(map[whatsapp.JID]bool),
connected: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_connected",
Help: "Bridge users connected to WhatsApp",
}),
connectedState: make(map[types.WhatsAppID]bool),
connectedState: make(map[whatsapp.JID]bool),
syncLocked: promauto.NewGauge(prometheus.GaugeOpts{
Name: "bridge_sync_locked",
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{
Name: "bridge_buffer_size",
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()
}
func (mh *MetricsHandler) TrackLoginState(jid types.WhatsAppID, loggedIn bool) {
func (mh *MetricsHandler) TrackLoginState(jid whatsapp.JID, loggedIn bool) {
if !mh.running {
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 {
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 {
return
}

206
portal.go
View file

@ -40,25 +40,26 @@ import (
"sync"
"time"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/crypto/attachment"
"github.com/Rhymen/go-whatsapp"
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/crypto/attachment"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/pushrules"
"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 {
bridge.portalsLock.Lock()
defer bridge.portalsLock.Unlock()
@ -83,7 +84,7 @@ func (bridge *Bridge) GetAllPortals() []*Portal {
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))
}
@ -131,7 +132,7 @@ func (bridge *Bridge) NewManualPortal(key database.PortalKey) *Portal {
bridge: bridge,
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),
}
@ -146,7 +147,7 @@ func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
bridge: bridge,
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),
}
@ -171,7 +172,7 @@ type Portal struct {
roomCreateLock sync.Mutex
recentlyHandled [recentlyHandledLength]types.WhatsAppMessageID
recentlyHandled [recentlyHandledLength]whatsapp.MessageID
recentlyHandledLock sync.Mutex
recentlyHandledIndex uint8
@ -184,6 +185,7 @@ type Portal struct {
messages chan PortalMessage
isPrivate *bool
isBroadcast *bool
hasRelaybot *bool
}
@ -256,7 +258,7 @@ func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
portal.HandleLocationMessage(msg.source, data)
case whatsapp.StubMessage:
portal.HandleStubMessage(msg.source, data, isBackfill)
case whatsappExt.MessageRevocation:
case whatsapp.MessageRevocation:
portal.HandleMessageRevoke(msg.source, data)
case FakeMessage:
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
for i := start; i != start; i = (i - 1) % recentlyHandledLength {
if portal.recentlyHandled[i] == id {
@ -275,7 +277,7 @@ func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool {
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)
if msg != nil {
return true
@ -287,7 +289,7 @@ func init() {
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.Chat = portal.Key
msg.JID = message.GetKey().GetId()
@ -304,6 +306,7 @@ func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo,
}
}
msg.Content = message.Message
msg.Sent = isSent
msg.Insert()
portal.recentlyHandledLock.Lock()
@ -311,6 +314,7 @@ func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo,
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
portal.recentlyHandledLock.Unlock()
portal.recentlyHandled[index] = msg.JID
return msg
}
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) {
portal.markHandled(source, message, mxid)
portal.markHandled(source, message, mxid, true)
portal.sendDeliveryReceipt(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
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil {
levels = portal.GetBasePowerLevels()
changed = true
}
participantMap := make(map[string]bool)
participantMap := make(map[whatsapp.JID]bool)
for _, participant := range metadata.Participants {
participantMap[participant.JID] = true
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)
}
}
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)
}
}
}
}
}
portal.kickExtraUsers(participantMap)
}
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) {
var err error
avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID)
@ -464,7 +486,10 @@ func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInf
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 {
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
portal.Name = name
@ -488,7 +513,7 @@ func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *ap
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 {
portal.log.Debugfln("Updating topic %s -> %s", 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 {
if portal.IsPrivateChat() {
return false
} else if portal.IsStatusBroadcastRoom() {
} else if portal.IsStatusBroadcastList() {
update := false
update = portal.UpdateName("WhatsApp Status Broadcast", "", nil, false) || update
update = portal.UpdateTopic("WhatsApp status updates from your contacts", "", nil, false) || update
update = portal.UpdateName(StatusBroadcastName, "", 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
}
metadata, err := user.Conn.GetGroupMetaData(portal.Key.JID)
@ -597,13 +634,9 @@ func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
portal.ensureUserInvited(user)
}
if portal.IsPrivateChat() {
return
}
update := false
update = portal.UpdateMetadata(user) || update
if !portal.IsStatusBroadcastRoom() && portal.Avatar == "" {
if !portal.IsPrivateChat() && !portal.IsBroadcastList() && portal.Avatar == "" {
update = portal.UpdateAvatar(user, nil, false) || update
}
if update {
@ -739,6 +772,10 @@ func (portal *Portal) BackfillHistory(user *User, lastMessageTime uint64) error
for len(lastMessageID) > 0 {
portal.log.Debugln("Fetching 50 messages of history after", lastMessageID)
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 {
return err
}
@ -974,7 +1011,8 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
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() {
puppet := portal.bridge.GetPuppetByJID(portal.Key.JID)
if portal.bridge.Config.Bridge.PrivateChatPortalMeta {
@ -985,9 +1023,22 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
portal.Name = ""
}
portal.Topic = "WhatsApp private chat"
} else if portal.IsStatusBroadcastRoom() {
} else if portal.IsStatusBroadcastList() {
portal.Name = "WhatsApp Status Broadcast"
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 {
var err error
metadata, err = user.Conn.GetGroupMetaData(portal.Key.JID)
@ -1076,6 +1127,9 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
_ = customPuppet.CustomIntent().EnsureJoined(portal.MXID)
}
}
if broadcastMetadata != nil {
portal.SyncBroadcastRecipients(broadcastMetadata)
}
user.addPortalToCommunity(portal)
if portal.IsPrivateChat() {
puppet := user.bridge.GetPuppetByJID(portal.Key.JID)
@ -1099,12 +1153,24 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
func (portal *Portal) IsPrivateChat() bool {
if portal.isPrivate == nil {
val := strings.HasSuffix(portal.Key.JID, whatsappExt.NewUserSuffix)
val := strings.HasSuffix(portal.Key.JID, whatsapp.NewUserSuffix)
portal.isPrivate = &val
}
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 {
if portal.bridge.Relaybot == nil {
return false
@ -1115,10 +1181,6 @@ func (portal *Portal) HasRelaybot() bool {
return *portal.hasRelaybot
}
func (portal *Portal) IsStatusBroadcastRoom() bool {
return portal.Key.JID == "status@broadcast"
}
func (portal *Portal) MainIntent() *appservice.IntentAPI {
if portal.IsPrivateChat() {
return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent()
@ -1152,7 +1214,7 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp
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)
if msg == nil || msg.IsFakeMXID() {
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) {
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
// However, broadcast lists don't have update commands, so we handle these if it's not a backfill
return
}
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")
case waProto.WebMessageInfo_GROUP_CHANGE_RESTRICT:
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)
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)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_PROMOTE:
eventID = portal.ChangeAdminStatus(message.Params, true)
@ -1326,7 +1389,7 @@ func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessa
if len(eventID) == 0 {
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) {
@ -1496,6 +1559,7 @@ func (portal *Portal) HandleWhatsAppKick(source *User, senderJID string, jids []
puppet := portal.bridge.GetPuppetByJID(jid)
portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
if !portal.IsBroadcastList() {
user := portal.bridge.GetUserByJID(jid)
if user != nil {
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)
}
}
}
}
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",
"-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'",
outputFileName)
vcLog := portal.log.Sub("VideoConverter").WithDefaultLevel(log.LevelWarn)
vcLog := portal.log.Sub("VideoConverter").Writer(log.LevelWarn)
cmd.Stdout = 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 {
var caption string
var mentionedJIDs []types.WhatsAppID
var mentionedJIDs []whatsapp.JID
if relaybotFormatted {
caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
}
@ -1851,7 +1916,7 @@ func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool
type MediaUpload struct {
Caption string
MentionedJIDs []types.WhatsAppID
MentionedJIDs []whatsapp.JID
URL string
MediaKey []byte
FileEncSHA256 []byte
@ -2091,12 +2156,12 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
if info == nil {
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.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)
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 {
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())
}
} else {
portal.log.Debugfln("Handled Matrix event %s", evt.ID)
portal.sendDeliveryReceipt(evt.ID)
dbMsg.MarkSent()
}
if errorEventID != "" {
_, err = portal.MainIntent().RedactEvent(portal.MXID, errorEventID)
@ -2322,7 +2382,7 @@ func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
return
}
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:
return
}

View file

@ -27,12 +27,11 @@ import (
"time"
"github.com/gorilla/websocket"
log "maunium.net/go/maulogger/v2"
"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 {
@ -126,7 +125,7 @@ func (prov *ProvisioningAPI) DeleteSession(w http.ResponseWriter, r *http.Reques
})
return
}
user.Disconnect()
user.DeleteConnection()
user.SetSession(nil)
jsonResponse(w, http.StatusOK, Response{true, "Session information purged"})
}
@ -140,7 +139,7 @@ func (prov *ProvisioningAPI) DeleteConnection(w http.ResponseWriter, r *http.Req
})
return
}
user.Disconnect()
user.DeleteConnection()
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
}
sess, err := user.Conn.Disconnect()
err := user.Conn.Disconnect()
if err == whatsapp.ErrNotConnected {
jsonResponse(w, http.StatusNotFound, Error{
Error: "You were not connected",
@ -167,8 +166,6 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request)
ErrCode: err.Error(),
})
return
} else {
user.SetSession(&sess)
}
user.bridge.Metrics.TrackConnectionState(user.JID, false)
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")
wasConnected := true
sess, err := user.Conn.Disconnect()
err := user.Conn.Disconnect()
if err == whatsapp.ErrNotConnected {
wasConnected = false
} else if err != nil {
user.log.Warnln("Error while disconnecting:", err)
} else {
user.SetSession(&sess)
}
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)
if err == whatsapp.ErrInvalidSession {
if user.Session != nil {
user.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
sess, err = user.Conn.RestoreWithSession(*user.Session)
if err == nil {
user.SetSession(&sess)
}
user.Conn.SetSession(*user.Session)
err = user.Conn.Restore(true, r.Context())
} else {
jsonResponse(w, http.StatusForbidden, Error{
Error: "You're not logged in",
@ -217,7 +210,8 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
})
return
}
} else if err == whatsapp.ErrLoginInProgress {
}
if err == whatsapp.ErrLoginInProgress {
jsonResponse(w, http.StatusConflict, Error{
Error: "A login or reconnection is already in progress.",
ErrCode: "login in progress",
@ -232,23 +226,14 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
user.log.Warnln("Error while reconnecting:", err)
if errors.Is(err, whatsapp.ErrRestoreSessionTimeout) {
jsonResponse(w, http.StatusForbidden, Error{
Error: "Reconnection timed out. Is WhatsApp on your phone reachable?",
ErrCode: err.Error(),
})
} else {
jsonResponse(w, http.StatusForbidden, Error{
jsonResponse(w, http.StatusInternalServerError, Error{
Error: fmt.Sprintf("Unknown error while reconnecting: %v", err),
ErrCode: err.Error(),
})
}
user.log.Debugln("Disconnecting due to failed session restore in reconnect command...")
sess, err := user.Conn.Disconnect()
err = user.Conn.Disconnect()
if err != nil {
user.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
} else {
user.SetSession(&sess)
}
return
}
@ -340,7 +325,7 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
return
}
}
user.Disconnect()
user.DeleteConnection()
}
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")
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"
if err != nil {
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."
} else if errors.Is(err, whatsapp.ErrInvalidWebsocket) {
msg = "WhatsApp connection error. Please try again."
user.Disconnect()
// TODO might need to make sure it reconnects?
} else {
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
}
user.log.Debugln("Successful login via provisioning API")
user.log.Debugln("Successful login as", jid, "via provisioning API")
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.SetSession(&session)
_ = c.WriteJSON(map[string]interface{}{

View file

@ -22,21 +22,18 @@ import (
"regexp"
"strings"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
)
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 {
userIDRegex = regexp.MustCompile(fmt.Sprintf("^@%s:%s$",
bridge.Config.Bridge.FormatUsername("([0-9]+)"),
@ -47,7 +44,7 @@ func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.WhatsAppID, bool) {
return "", false
}
jid := types.WhatsAppID(match[1] + whatsappExt.NewUserSuffix)
jid := whatsapp.JID(match[1] + whatsapp.NewUserSuffix)
return jid, true
}
@ -60,7 +57,7 @@ func (bridge *Bridge) GetPuppetByMXID(mxid id.UserID) *Puppet {
return bridge.GetPuppetByJID(jid)
}
func (bridge *Bridge) GetPuppetByJID(jid types.WhatsAppID) *Puppet {
func (bridge *Bridge) GetPuppetByJID(jid whatsapp.JID) *Puppet {
bridge.puppetsLock.Lock()
defer bridge.puppetsLock.Unlock()
puppet, ok := bridge.puppets[jid]
@ -125,12 +122,12 @@ func (bridge *Bridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Puppet
return output
}
func (bridge *Bridge) FormatPuppetMXID(jid types.WhatsAppID) id.UserID {
func (bridge *Bridge) FormatPuppetMXID(jid whatsapp.JID) id.UserID {
return id.NewUserID(
bridge.Config.Bridge.FormatUsername(
strings.Replace(
jid,
whatsappExt.NewUserSuffix, "", 1)),
whatsapp.NewUserSuffix, "", 1)),
bridge.Config.Homeserver.Domain)
}
@ -161,7 +158,7 @@ type Puppet struct {
}
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 {
@ -181,7 +178,7 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
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 {
var err error
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)
}
if contact.Jid == source.JID {
contact.Notify = source.Conn.Info.Pushname
if contact.JID == source.JID {
contact.Notify = source.pushName
}
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
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -40,13 +41,11 @@ import (
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix-whatsapp/database"
"maunium.net/go/mautrix-whatsapp/types"
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
)
type User struct {
*database.User
Conn *whatsappExt.ExtendedConn
Conn *whatsapp.Conn
bridge *Bridge
log log.Logger
@ -63,6 +62,7 @@ type User struct {
cleanDisconnection bool
batteryWarningsSent int
lastReconnection int64
pushName string
chatListReceived chan struct{}
syncPortalsDone chan struct{}
@ -75,6 +75,7 @@ type User struct {
mgmtCreateLock sync.Mutex
connLock sync.Mutex
cancelReconnect func()
}
func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
@ -91,7 +92,7 @@ func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
return user
}
func (bridge *Bridge) GetUserByJID(userID types.WhatsAppID) *User {
func (bridge *Bridge) GetUserByJID(userID whatsapp.JID) *User {
bridge.usersLock.Lock()
defer bridge.usersLock.Unlock()
user, ok := bridge.usersByJID[userID]
@ -236,55 +237,56 @@ func (user *User) SetSession(session *whatsapp.Session) {
func (user *User) Connect(evenIfNoSession bool) bool {
user.connLock.Lock()
if user.Conn != nil && user.Conn.IsConnected() {
if user.Conn != nil {
user.connLock.Unlock()
if user.Conn.IsConnected() {
return true
} else {
return user.RestoreSession()
}
} else if !evenIfNoSession && user.Session == nil {
user.connLock.Unlock()
return false
}
if user.Conn != nil {
user.Disconnect()
}
user.log.Debugln("Connecting to WhatsApp")
timeout := time.Duration(user.bridge.Config.Bridge.ConnectionTimeout)
if timeout == 0 {
timeout = 20
}
conn, err := whatsapp.NewConnWithOptions(&whatsapp.Options{
user.Conn = whatsapp.NewConn(&whatsapp.Options{
Timeout: timeout * time.Second,
LongClientName: user.bridge.Config.WhatsApp.OSName,
ShortClientName: user.bridge.Config.WhatsApp.BrowserName,
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()
return user.RestoreSession()
}
func (user *User) Disconnect() {
sess, err := user.Conn.Disconnect()
func (user *User) DeleteConnection() {
user.connLock.Lock()
if user.Conn == nil {
user.connLock.Unlock()
return
}
err := user.Conn.Disconnect()
if err != nil && err != whatsapp.ErrNotConnected {
user.log.Warnln("Error disconnecting: %v", err)
}
user.SetSession(&sess)
user.Conn.RemoveHandlers()
user.Conn = nil
user.bridge.Metrics.TrackConnectionState(user.JID, false)
user.connLock.Unlock()
}
func (user *User) RestoreSession() bool {
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 {
return true
} else if err != nil {
@ -292,24 +294,23 @@ func (user *User) RestoreSession() bool {
if errors.Is(err, whatsapp.ErrUnpaired) {
user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " +
"To re-pair your phone, log in again.")
user.Disconnect()
user.removeFromJIDMap()
//user.JID = ""
user.SetSession(nil)
user.DeleteConnection()
return false
} else {
user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " +
"on your phone is reachable and use `reconnect` to try connecting again.")
}
user.log.Debugln("Disconnecting due to failed session restore...")
_, err := user.Conn.Disconnect()
err = user.Conn.Disconnect()
if err != nil {
user.log.Errorln("Failed to disconnect after failed session restore:", err)
}
return false
}
user.ConnectionErrors = 0
user.SetSession(&sess)
user.log.Debugln("Session restored successfully")
user.PostLogin()
}
@ -384,7 +385,7 @@ func (user *User) Login(ce *CommandEvent) {
qrChan := make(chan string, 3)
eventIDChan := make(chan id.EventID, 1)
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"
if err != nil {
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
// Also between the two logout methods (commands.go and provisioning.go)
user.log.Debugln("Successful login as", jid, "via command")
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.SetSession(&session)
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 {
if user.Conn != conn {
user.log.Warnln("Connection changed before scheduled post-connection ping, canceling ping")
return false
}
func (user *User) postConnPing() bool {
user.log.Debugln("Making post-connection ping")
err := conn.AdminTest()
if err != nil {
var err error
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)
sess, disconnectErr := conn.Disconnect()
disconnectErr := user.Conn.Disconnect()
if disconnectErr != nil {
user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr)
} else {
user.Session = &sess
}
user.bridge.Metrics.TrackDisconnection(user.MXID)
go func() {
@ -522,13 +528,9 @@ func (user *User) postConnPing(conn *whatsappExt.ExtendedConn) bool {
user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
}()
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()
user.lastReconnection = time.Now().Unix()
user.createCommunity()
@ -540,11 +542,11 @@ func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) {
user.log.Debugln("Chat list receive confirmation received in PostLogin")
case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second):
user.log.Warnln("Timed out waiting for chat list to arrive!")
user.postConnPing(conn)
user.postConnPing()
return
}
if !user.postConnPing(conn) {
if !user.postConnPing() {
user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.")
return
}
@ -559,16 +561,68 @@ func (user *User) intPostLogin(conn *whatsappExt.ExtendedConn) {
}
}
func (user *User) HandleStreamEvent(evt whatsappExt.StreamEvent) {
if evt.Type == whatsappExt.StreamSleep {
type NormalMessage interface {
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() {
user.lastReconnection = 0
user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds")
conn := user.Conn
go func() {
time.Sleep(20 * time.Second)
// 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 {
@ -580,10 +634,10 @@ func (user *User) HandleChatList(chats []whatsapp.Chat) {
user.log.Infoln("Chat list received")
chatMap := make(map[string]whatsapp.Chat)
for _, chat := range user.Conn.Store.Chats {
chatMap[chat.Jid] = chat
chatMap[chat.JID] = chat
}
for _, chat := range chats {
chatMap[chat.Jid] = chat
chatMap[chat.JID] = chat
}
select {
case user.chatListReceived <- struct{}{}:
@ -605,14 +659,14 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
for _, chat := range chatMap {
ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64)
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
}
portal := user.GetPortalByJID(chat.Jid)
portal := user.GetPortalByJID(chat.JID)
chats = append(chats, Chat{
Portal: portal,
Contact: user.Conn.Store.Contacts[chat.Jid],
Contact: user.Conn.Store.Contacts[chat.JID],
LastMessageTime: ts,
})
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) {
contactMap := make(map[string]whatsapp.Contact)
contactMap := make(map[whatsapp.JID]whatsapp.Contact)
for _, contact := range contacts {
contactMap[contact.Jid] = contact
contactMap[contact.JID] = contact
}
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 {
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")
for jid, contact := range contacts {
if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(contact.Jid)
if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(contact.JID)
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")
@ -796,14 +863,18 @@ func (user *User) tryReconnect(msg string) {
baseDelay = -baseDelay + 1
}
delay := baseDelay
conn := user.Conn
takeover := false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
user.cancelReconnect = cancel
for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
if user.Conn != conn {
user.log.Debugln("Connection was recreated, aborting reconnection attempts")
select {
case <-ctx.Done():
user.log.Debugln("tryReconnect context cancelled, aborting reconnection attempts")
return
default:
}
err := conn.Restore(takeover)
err := user.Conn.Restore(takeover, ctx)
takeover = true
if err == nil {
user.ConnectionErrors = 0
@ -813,14 +884,23 @@ func (user *User) tryReconnect(msg string) {
user.PostLogin()
return
} else if errors.Is(err, whatsapp.ErrBadRequest) {
user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...")
sess, err := conn.Disconnect()
user.log.Warnln("Got init 400 error when trying to reconnect, resetting connection...")
err = user.Conn.Disconnect()
if err != nil {
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)
}
tries++
user.ConnectionErrors++
if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
@ -841,19 +921,11 @@ func (user *User) tryReconnect(msg string) {
}
}
func (user *User) ShouldCallSynchronously() bool {
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 {
func (user *User) PortalKey(jid whatsapp.JID) database.PortalKey {
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))
}
@ -888,13 +960,16 @@ func (user *User) handleMessageLoop() {
func (user *User) HandleNewContact(contact whatsapp.Contact) {
user.log.Debugfln("Contact message: %+v", contact)
go func() {
if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) {
contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
if strings.HasSuffix(contact.JID, whatsapp.OldUserSuffix) {
contact.JID = strings.Replace(contact.JID, whatsapp.OldUserSuffix, whatsapp.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)
}()
} 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) {
@ -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 {
Text string
ID string
Alert bool
}
func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
func (user *User) HandleCallInfo(info whatsapp.CallInfo) {
if info.Data != nil {
return
}
@ -968,19 +1003,19 @@ func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
ID: info.ID,
}
switch info.Type {
case whatsappExt.CallOffer:
case whatsapp.CallOffer:
if !user.bridge.Config.Bridge.CallNotices.Start {
return
}
data.Text = "Incoming call"
data.Alert = true
case whatsappExt.CallOfferVideo:
case whatsapp.CallOfferVideo:
if !user.bridge.Config.Bridge.CallNotices.Start {
return
}
data.Text = "Incoming video call"
data.Alert = true
case whatsappExt.CallTerminate:
case whatsapp.CallTerminate:
if !user.bridge.Config.Bridge.CallNotices.End {
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)
switch info.Status {
case whatsapp.PresenceUnavailable:
@ -1023,14 +1058,13 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
}
}
func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead {
func (user *User) HandleMsgInfo(info whatsapp.JSONMsgInfo) {
if (info.Command == whatsapp.MsgInfoCommandAck || info.Command == whatsapp.MsgInfoCommandAcks) && info.Acknowledgement == whatsapp.AckMessageRead {
portal := user.GetPortalByJID(info.ToJID)
if len(portal.MXID) == 0 {
return
}
go func() {
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
for _, msgID := range info.IDs {
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)
}
}
}()
}
}
func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
if received.Type == "read" {
user.markSelfRead(received.Jid, received.Index)
go user.markSelfRead(received.Jid, received.Index)
} else {
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) {
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) {
if strings.HasSuffix(jid, whatsappExt.OldUserSuffix) {
jid = strings.Replace(jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
if strings.HasSuffix(jid, whatsapp.OldUserSuffix) {
jid = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
}
puppet := user.bridge.GetPuppetByJID(user.JID)
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 {
case whatsappExt.CommandPicture:
if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) {
case whatsapp.CommandPicture:
if strings.HasSuffix(cmd.JID, whatsapp.NewUserSuffix) {
puppet := user.bridge.GetPuppetByJID(cmd.JID)
go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
} else if user.bridge.Config.Bridge.ChatMetaSync {
portal := user.GetPortalByJID(cmd.JID)
go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
}
case whatsappExt.CommandDisconnect:
case whatsapp.CommandDisconnect:
if cmd.Kind == "replaced" {
user.cleanDisconnection = true
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) {
if cmd.Command != whatsappExt.ChatUpdateCommandAction {
func (user *User) HandleChatUpdate(cmd whatsapp.ChatUpdate) {
if cmd.Command != whatsapp.ChatUpdateCommandAction {
return
}
portal := user.GetPortalByJID(cmd.JID)
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() {
err := portal.CreateMatrixRoom(user)
if err != nil {
@ -1139,11 +1172,11 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
// These don't come down the message history :(
switch cmd.Data.Action {
case whatsappExt.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil,true)
case whatsappExt.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, nil,true)
case whatsappExt.ChatActionRemove:
case whatsapp.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil, true)
case whatsapp.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, nil, true)
case whatsapp.ChatActionRemove:
// 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.
if !user.bridge.Config.Bridge.ChatMetaSync {
@ -1162,45 +1195,48 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
}
switch cmd.Data.Action {
case whatsappExt.ChatActionNameChange:
case whatsapp.ChatActionNameChange:
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)
case whatsappExt.ChatActionDemote:
case whatsapp.ChatActionDemote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
case whatsappExt.ChatActionAnnounce:
case whatsapp.ChatActionAnnounce:
go portal.RestrictMessageSending(cmd.Data.Announce)
case whatsappExt.ChatActionRestrict:
case whatsapp.ChatActionRestrict:
go portal.RestrictMetadataChanges(cmd.Data.Restrict)
case whatsappExt.ChatActionRemove:
case whatsapp.ChatActionRemove:
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)
case whatsappExt.ChatActionIntroduce:
case whatsapp.ChatActionIntroduce:
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) {
var msg json.RawMessage
err := json.Unmarshal([]byte(message), &msg)
if err != nil {
func (user *User) HandleConnInfo(info whatsapp.ConnInfo) {
if user.Session != nil && info.Connected && len(info.ClientToken) > 0 {
user.log.Debugln("Received new tokens")
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
}
user.log.Debugln("JSON message:", message)
user.log.Debugfln("JSON message: %s", message)
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 {
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
}