forked from MirrorHub/mautrix-whatsapp
Add basic Matrix puppeting support
May contain bugs. EDUs from /sync are not yet handled.
This commit is contained in:
parent
95e62fae77
commit
2c9c473040
14 changed files with 379 additions and 55 deletions
|
@ -59,7 +59,7 @@
|
|||
* [ ] When receiving invite<sup>[2]</sup>
|
||||
* [x] When receiving message
|
||||
* [ ] Private chat creation by inviting Matrix puppet of WhatsApp user to new room
|
||||
* [ ] Option to use own Matrix account for messages sent from WhatsApp mobile/other web clients
|
||||
* [x] Option to use own Matrix account for messages sent from WhatsApp mobile/other web clients
|
||||
* [x] Shared group chat portals
|
||||
|
||||
<sup>[1]</sup> May involve reverse-engineering the WhatsApp Web API and/or editing go-whatsapp
|
||||
|
|
34
commands.go
34
commands.go
|
@ -18,10 +18,12 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/format"
|
||||
"strings"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
|
||||
"maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix-appservice"
|
||||
|
@ -80,6 +82,8 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes
|
|||
switch cmd {
|
||||
case "login":
|
||||
handler.CommandLogin(ce)
|
||||
case "logout-matrix":
|
||||
handler.CommandLogoutMatrix(ce)
|
||||
case "help":
|
||||
handler.CommandHelp(ce)
|
||||
case "reconnect":
|
||||
|
@ -92,7 +96,7 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes
|
|||
handler.CommandDeleteSession(ce)
|
||||
case "delete-portal":
|
||||
handler.CommandDeletePortal(ce)
|
||||
case "logout", "sync", "list", "open", "pm":
|
||||
case "login-matrix", "logout", "sync", "list", "open", "pm":
|
||||
if ce.User.Conn == nil {
|
||||
ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.")
|
||||
return
|
||||
|
@ -102,6 +106,8 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes
|
|||
}
|
||||
|
||||
switch cmd {
|
||||
case "login-matrix":
|
||||
handler.CommandLoginMatrix(ce)
|
||||
case "logout":
|
||||
handler.CommandLogout(ce)
|
||||
case "sync":
|
||||
|
@ -433,3 +439,25 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
|
|||
}
|
||||
ce.Reply("Created portal room and invited you to it.")
|
||||
}
|
||||
|
||||
const cmdLoginMatrixHelp = `login-matrix <_access token_> - Replace your WhatsApp account's Matrix puppet with your real Matrix account.'`
|
||||
|
||||
func (handler *CommandHandler) CommandLoginMatrix(ce *CommandEvent) {
|
||||
if len(ce.Args) == 0 {
|
||||
ce.Reply("**Usage:** `login-matrix <access token>`")
|
||||
return
|
||||
}
|
||||
puppet := handler.bridge.GetPuppetByJID(ce.User.JID)
|
||||
err := puppet.SwitchCustomMXID(ce.Args[0], ce.User.MXID)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to switch puppet: %v", err)
|
||||
return
|
||||
}
|
||||
ce.Reply("Successfully switched puppet")
|
||||
}
|
||||
|
||||
const cmdLogoutMatrixHelp = `logout-matrix - Switch your WhatsApp account's Matrix puppet back to the default one.`
|
||||
|
||||
func (handler *CommandHandler) CommandLogoutMatrix(ce *CommandEvent) {
|
||||
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ type BridgeConfig struct {
|
|||
RecoverHistory bool `yaml:"recovery_history_backfill"`
|
||||
SyncChatMaxAge uint64 `yaml:"sync_max_chat_age"`
|
||||
|
||||
SyncWithCustomPuppets bool `yaml:"sync_with_custom_puppets"`
|
||||
|
||||
CommandPrefix string `yaml:"command_prefix"`
|
||||
|
||||
Permissions PermissionConfig `yaml:"permissions"`
|
||||
|
@ -61,6 +63,8 @@ func (bc *BridgeConfig) setDefaults() {
|
|||
bc.RecoverChatSync = -1
|
||||
bc.RecoverHistory = true
|
||||
bc.SyncChatMaxAge = 259200
|
||||
|
||||
bc.SyncWithCustomPuppets = true
|
||||
}
|
||||
|
||||
type umBridgeConfig BridgeConfig
|
||||
|
|
168
custompuppet.go
Normal file
168
custompuppet.go
Normal file
|
@ -0,0 +1,168 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix-appservice"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoCustomMXID = errors.New("no custom mxid set")
|
||||
ErrMismatchingMXID = errors.New("whoami result does not match custom mxid")
|
||||
)
|
||||
|
||||
func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid string) error {
|
||||
prevCustomMXID := puppet.CustomMXID
|
||||
if puppet.customIntent != nil {
|
||||
puppet.stopSyncing()
|
||||
}
|
||||
puppet.CustomMXID = mxid
|
||||
puppet.AccessToken = accessToken
|
||||
|
||||
err := puppet.StartCustomMXID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(prevCustomMXID) > 0 {
|
||||
delete(puppet.bridge.puppetsByCustomMXID, prevCustomMXID)
|
||||
}
|
||||
if len(puppet.CustomMXID) > 0 {
|
||||
puppet.bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet
|
||||
}
|
||||
puppet.Update()
|
||||
// TODO leave rooms with default puppet
|
||||
return nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
|
||||
if len(puppet.CustomMXID) == 0 {
|
||||
return nil, ErrNoCustomMXID
|
||||
}
|
||||
client, err := mautrix.NewClient(puppet.bridge.AS.HomeserverURL, puppet.CustomMXID, puppet.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.Store = puppet
|
||||
|
||||
ia := puppet.bridge.AS.NewIntentAPI("custom")
|
||||
ia.Client = client
|
||||
ia.Localpart = puppet.CustomMXID[1:strings.IndexRune(puppet.CustomMXID, ':')]
|
||||
ia.UserID = puppet.CustomMXID
|
||||
ia.IsCustomPuppet = true
|
||||
return ia, nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) StartCustomMXID() error {
|
||||
if len(puppet.CustomMXID) == 0 {
|
||||
return nil
|
||||
}
|
||||
intent, err := puppet.newCustomIntent()
|
||||
if err != nil {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
return err
|
||||
}
|
||||
urlPath := intent.BuildURL("account", "whoami")
|
||||
var resp struct{ UserID string `json:"user_id"` }
|
||||
_, err = intent.MakeRequest("GET", urlPath, nil, &resp)
|
||||
if err != nil {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
return err
|
||||
}
|
||||
if resp.UserID != puppet.CustomMXID {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
return ErrMismatchingMXID
|
||||
}
|
||||
puppet.customIntent = intent
|
||||
puppet.startSyncing()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) startSyncing() {
|
||||
if !puppet.bridge.Config.Bridge.SyncWithCustomPuppets {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
puppet.log.Debugln("Starting syncing...")
|
||||
err := puppet.customIntent.Sync()
|
||||
if err != nil {
|
||||
puppet.log.Errorln("Fatal error syncing:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (puppet *Puppet) stopSyncing() {
|
||||
if !puppet.bridge.Config.Bridge.SyncWithCustomPuppets {
|
||||
return
|
||||
}
|
||||
puppet.customIntent.StopSync()
|
||||
}
|
||||
|
||||
func (puppet *Puppet) ProcessResponse(resp *mautrix.RespSync, since string) error {
|
||||
puppet.log.Debugln("Sync data:", resp, since)
|
||||
// TODO handle sync data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) OnFailedSync(res *mautrix.RespSync, err error) (time.Duration, error) {
|
||||
puppet.log.Warnln("Sync error:", err)
|
||||
return 10 * time.Second, nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) GetFilterJSON(_ string) json.RawMessage {
|
||||
mxid, _ := json.Marshal(puppet.CustomMXID)
|
||||
return json.RawMessage(fmt.Sprintf(`{
|
||||
"account_data": { "types": [] },
|
||||
"presence": {
|
||||
"senders": [
|
||||
%s
|
||||
],
|
||||
"types": [
|
||||
"m.presence"
|
||||
]
|
||||
},
|
||||
"room": {
|
||||
"ephemeral": {
|
||||
"types": [
|
||||
"m.typing",
|
||||
"m.receipt"
|
||||
]
|
||||
},
|
||||
"include_leave": false,
|
||||
"account_data": { "types": [] },
|
||||
"state": { "types": [] },
|
||||
"timeline": { "types": [] }
|
||||
}
|
||||
}`, mxid))
|
||||
}
|
||||
|
||||
func (puppet *Puppet) SaveFilterID(_, _ string) {}
|
||||
func (puppet *Puppet) SaveNextBatch(_, nbt string) { puppet.NextBatch = nbt }
|
||||
func (puppet *Puppet) SaveRoom(room *mautrix.Room) {}
|
||||
func (puppet *Puppet) LoadFilterID(_ string) string { return "" }
|
||||
func (puppet *Puppet) LoadNextBatch(_ string) string { return puppet.NextBatch }
|
||||
func (puppet *Puppet) LoadRoom(roomID string) *mautrix.Room { return nil }
|
|
@ -56,6 +56,26 @@ func (pq *PuppetQuery) Get(jid types.WhatsAppID) *Puppet {
|
|||
return pq.New().Scan(row)
|
||||
}
|
||||
|
||||
func (pq *PuppetQuery) GetByCustomMXID(mxid types.MatrixUserID) *Puppet {
|
||||
row := pq.db.QueryRow("SELECT * FROM puppet WHERE custom_mxid=$1", mxid)
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
return pq.New().Scan(row)
|
||||
}
|
||||
|
||||
func (pq *PuppetQuery) GetAllWithCustomMXID() (puppets []*Puppet) {
|
||||
rows, err := pq.db.Query("SELECT * FROM puppet WHERE custom_mxid<>''")
|
||||
if err != nil || rows == nil {
|
||||
return nil
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
puppets = append(puppets, pq.New().Scan(rows))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Puppet struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
|
@ -64,12 +84,16 @@ type Puppet struct {
|
|||
Avatar string
|
||||
Displayname string
|
||||
NameQuality int8
|
||||
|
||||
CustomMXID string
|
||||
AccessToken string
|
||||
NextBatch string
|
||||
}
|
||||
|
||||
func (puppet *Puppet) Scan(row Scannable) *Puppet {
|
||||
var displayname, avatar sql.NullString
|
||||
var displayname, avatar, customMXID, accessToken, nextBatch sql.NullString
|
||||
var quality sql.NullInt64
|
||||
err := row.Scan(&puppet.JID, &avatar, &displayname, &quality)
|
||||
err := row.Scan(&puppet.JID, &avatar, &displayname, &quality, &customMXID, &accessToken, &nextBatch)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
puppet.log.Errorln("Database scan failed:", err)
|
||||
|
@ -79,20 +103,23 @@ func (puppet *Puppet) Scan(row Scannable) *Puppet {
|
|||
puppet.Displayname = displayname.String
|
||||
puppet.Avatar = avatar.String
|
||||
puppet.NameQuality = int8(quality.Int64)
|
||||
puppet.CustomMXID = customMXID.String
|
||||
puppet.AccessToken = accessToken.String
|
||||
puppet.NextBatch = nextBatch.String
|
||||
return puppet
|
||||
}
|
||||
|
||||
func (puppet *Puppet) Insert() {
|
||||
_, err := puppet.db.Exec("INSERT INTO puppet VALUES ($1, $2, $3, $4)",
|
||||
puppet.JID, puppet.Avatar, puppet.Displayname, puppet.NameQuality)
|
||||
_, err := puppet.db.Exec("INSERT INTO puppet VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
puppet.JID, puppet.Avatar, puppet.Displayname, puppet.NameQuality, puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch)
|
||||
if err != nil {
|
||||
puppet.log.Warnfln("Failed to insert %s: %v", puppet.JID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (puppet *Puppet) Update() {
|
||||
_, err := puppet.db.Exec("UPDATE puppet SET displayname=$1, name_quality=$2, avatar=$3 WHERE jid=$4",
|
||||
puppet.Displayname, puppet.NameQuality, puppet.Avatar, puppet.JID)
|
||||
_, err := puppet.db.Exec("UPDATE puppet SET displayname=$1, name_quality=$2, avatar=$3, custom_mxid=$4, access_token=$5, next_batch=$6 WHERE jid=$7",
|
||||
puppet.Displayname, puppet.NameQuality, puppet.Avatar, puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch, puppet.JID)
|
||||
if err != nil {
|
||||
puppet.log.Warnfln("Failed to update %s->%s: %v", puppet.JID, err)
|
||||
}
|
||||
|
|
23
database/upgrades/2019-05-23-puppet-custom-mxid-columns.go
Normal file
23
database/upgrades/2019-05-23-puppet-custom-mxid-columns.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package upgrades
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
upgrades[5] = upgrade{"Add columns to store custom puppet info", func(dialect Dialect, tx *sql.Tx, db *sql.DB) error {
|
||||
_, err := tx.Exec(`ALTER TABLE puppet ADD COLUMN custom_mxid VARCHAR(255)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(`ALTER TABLE puppet ADD COLUMN access_token VARCHAR(1023)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(`ALTER TABLE puppet ADD COLUMN next_batch VARCHAR(255)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
|
@ -22,7 +22,9 @@ type upgrade struct {
|
|||
fn upgradeFunc
|
||||
}
|
||||
|
||||
var upgrades [5]upgrade
|
||||
const NumberOfUpgrades = 6
|
||||
|
||||
var upgrades [NumberOfUpgrades]upgrade
|
||||
|
||||
func getVersion(dialect Dialect, db *sql.DB) (int, error) {
|
||||
_, err := db.Exec("CREATE TABLE IF NOT EXISTS version (version INTEGER)")
|
||||
|
@ -63,7 +65,7 @@ func Run(log log.Logger, dialectName string, db *sql.DB) error {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Infofln("Database currently on v%d, latest: v%d", version, len(upgrades))
|
||||
log.Infofln("Database currently on v%d, latest: v%d", version, NumberOfUpgrades)
|
||||
for i, upgrade := range upgrades[version:] {
|
||||
log.Infofln("Upgrading database to v%d: %s", version+i+1, upgrade.message)
|
||||
tx, err := db.Begin()
|
||||
|
|
|
@ -81,6 +81,10 @@ bridge:
|
|||
# Default is 3 days = 259200 seconds
|
||||
sync_max_chat_age: 259200
|
||||
|
||||
# Whether or not to sync with custom puppets to receive EDUs that
|
||||
# are not normally sent to appservices.
|
||||
sync_with_custom_puppets: true
|
||||
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: "!wa"
|
||||
|
||||
|
|
6
go.mod
6
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/lib/pq v1.1.1
|
||||
github.com/mattn/go-isatty v0.0.8 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
||||
|
@ -17,9 +18,10 @@ require (
|
|||
maunium.net/go/mauflag v1.0.0
|
||||
maunium.net/go/maulogger/v2 v2.0.0
|
||||
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190515215109-3e27638f3f1d
|
||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190515184712-aecd1f0cca6f
|
||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190523231710-8b9923f4ca89
|
||||
)
|
||||
|
||||
replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
|
||||
|
||||
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20190523194501-cc7603f853df
|
||||
//replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20190523194501-cc7603f853df
|
||||
replace github.com/Rhymen/go-whatsapp => ../../Go/go-whatsapp
|
||||
|
|
12
main.go
12
main.go
|
@ -80,6 +80,7 @@ type Bridge struct {
|
|||
portalsByJID map[database.PortalKey]*Portal
|
||||
portalsLock sync.Mutex
|
||||
puppets map[types.WhatsAppID]*Puppet
|
||||
puppetsByCustomMXID map[types.MatrixUserID]*Puppet
|
||||
puppetsLock sync.Mutex
|
||||
}
|
||||
|
||||
|
@ -91,6 +92,7 @@ func NewBridge() *Bridge {
|
|||
portalsByMXID: make(map[types.MatrixRoomID]*Portal),
|
||||
portalsByJID: make(map[database.PortalKey]*Portal),
|
||||
puppets: make(map[types.WhatsAppID]*Puppet),
|
||||
puppetsByCustomMXID: make(map[types.MatrixUserID]*Puppet),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -192,6 +194,16 @@ func (bridge *Bridge) StartUsers() {
|
|||
for _, user := range bridge.GetAllUsers() {
|
||||
go user.Connect(false)
|
||||
}
|
||||
bridge.Log.Debugln("Starting custom puppets")
|
||||
for _, puppet := range bridge.GetAllPuppetsWithCustomMXID() {
|
||||
go func() {
|
||||
puppet.log.Debugln("Starting custom puppet", puppet.CustomMXID)
|
||||
err := puppet.StartCustomMXID()
|
||||
if err != nil {
|
||||
puppet.log.Errorln("Failed to start custom puppet:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (bridge *Bridge) Stop() {
|
||||
|
|
|
@ -166,6 +166,10 @@ func (mx *MatrixHandler) HandleMessage(evt *mautrix.Event) {
|
|||
if _, isPuppet := mx.bridge.ParsePuppetMXID(evt.Sender); evt.Sender == mx.bridge.Bot.UserID || isPuppet {
|
||||
return
|
||||
}
|
||||
isCustomPuppet, ok := evt.Content.Raw["net.maunium.whatsapp.puppet"].(bool)
|
||||
if ok && isCustomPuppet && mx.bridge.GetPuppetByCustomMXID(evt.Sender) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
roomID := types.MatrixRoomID(evt.RoomID)
|
||||
user := mx.bridge.GetUserByMXID(types.MatrixUserID(evt.Sender))
|
||||
|
|
49
portal.go
49
portal.go
|
@ -281,12 +281,6 @@ func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
|
|||
changed = true
|
||||
}
|
||||
for _, participant := range metadata.Participants {
|
||||
puppet := portal.bridge.GetPuppetByJID(participant.JID)
|
||||
err := puppet.Intent().EnsureJoined(portal.MXID)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.JID, portal.MXID, err)
|
||||
}
|
||||
|
||||
user := portal.bridge.GetUserByJID(participant.JID)
|
||||
if user != nil && !portal.bridge.AS.StateStore.IsInvited(portal.MXID, user.MXID) {
|
||||
_, err = portal.MainIntent().InviteUser(portal.MXID, &mautrix.ReqInviteUser{
|
||||
|
@ -297,6 +291,12 @@ func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
puppet := portal.bridge.GetPuppetByJID(participant.JID)
|
||||
err := puppet.IntentFor(portal).EnsureJoined(portal.MXID)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.JID, portal.MXID, err)
|
||||
}
|
||||
|
||||
expectedLevel := 0
|
||||
if participant.IsSuperAdmin {
|
||||
expectedLevel = 95
|
||||
|
@ -363,7 +363,7 @@ func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID) bool {
|
|||
if portal.Name != name {
|
||||
intent := portal.MainIntent()
|
||||
if len(setBy) > 0 {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).Intent()
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
_, err := intent.SetRoomName(portal.MXID, name)
|
||||
if err == nil {
|
||||
|
@ -379,7 +379,7 @@ func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID) bool {
|
|||
if portal.Topic != topic {
|
||||
intent := portal.MainIntent()
|
||||
if len(setBy) > 0 {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).Intent()
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
_, err := intent.SetRoomTopic(portal.MXID, topic)
|
||||
if err == nil {
|
||||
|
@ -719,7 +719,7 @@ func (portal *Portal) IsStatusBroadcastRoom() bool {
|
|||
|
||||
func (portal *Portal) MainIntent() *appservice.IntentAPI {
|
||||
if portal.IsPrivateChat() {
|
||||
return portal.bridge.GetPuppetByJID(portal.Key.JID).Intent()
|
||||
return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent()
|
||||
}
|
||||
return portal.bridge.Bot
|
||||
}
|
||||
|
@ -727,10 +727,9 @@ func (portal *Portal) MainIntent() *appservice.IntentAPI {
|
|||
func (portal *Portal) GetMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI {
|
||||
if info.FromMe {
|
||||
if portal.IsPrivateChat() {
|
||||
// TODO handle own messages in private chats properly
|
||||
return nil
|
||||
return portal.bridge.GetPuppetByJID(user.JID).CustomIntent()
|
||||
}
|
||||
return portal.bridge.GetPuppetByJID(user.JID).Intent()
|
||||
return portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
||||
} else if portal.IsPrivateChat() {
|
||||
return portal.MainIntent()
|
||||
} else if len(info.SenderJid) == 0 {
|
||||
|
@ -740,7 +739,7 @@ func (portal *Portal) GetMessageIntent(user *User, info whatsapp.MessageInfo) *a
|
|||
return nil
|
||||
}
|
||||
}
|
||||
return portal.bridge.GetPuppetByJID(info.SenderJid).Intent()
|
||||
return portal.bridge.GetPuppetByJID(info.SenderJid).IntentFor(portal)
|
||||
}
|
||||
|
||||
func (portal *Portal) SetReply(content *mautrix.Content, info whatsapp.MessageInfo) {
|
||||
|
@ -765,15 +764,18 @@ func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.Messag
|
|||
if msg == nil {
|
||||
return
|
||||
}
|
||||
intent := portal.MainIntent()
|
||||
var intent *appservice.IntentAPI
|
||||
if message.FromMe {
|
||||
if portal.IsPrivateChat() {
|
||||
// TODO handle
|
||||
intent = portal.bridge.GetPuppetByJID(user.JID).CustomIntent()
|
||||
} else {
|
||||
intent = portal.bridge.GetPuppetByJID(user.JID).Intent()
|
||||
intent = portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
||||
}
|
||||
} else if len(message.Participant) > 0 {
|
||||
intent = portal.bridge.GetPuppetByJID(message.Participant).Intent()
|
||||
intent = portal.bridge.GetPuppetByJID(message.Participant).IntentFor(portal)
|
||||
}
|
||||
if intent == nil {
|
||||
intent = portal.MainIntent()
|
||||
}
|
||||
_, err := intent.RedactEvent(portal.MXID, msg.MXID)
|
||||
if err != nil {
|
||||
|
@ -783,6 +785,11 @@ func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.Messag
|
|||
msg.Delete()
|
||||
}
|
||||
|
||||
type MessageContent struct {
|
||||
*mautrix.Content
|
||||
IsCustomPuppet bool `json:"net.maunium.whatsapp.puppet,omitempty"`
|
||||
}
|
||||
|
||||
func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessage) {
|
||||
if len(portal.MXID) == 0 {
|
||||
return
|
||||
|
@ -808,7 +815,7 @@ func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessa
|
|||
portal.SetReply(content, message.Info)
|
||||
|
||||
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
||||
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, content, int64(message.Info.Timestamp*1000))
|
||||
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, &MessageContent{content, intent.IsCustomPuppet}, int64(message.Info.Timestamp*1000))
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err)
|
||||
return
|
||||
|
@ -891,7 +898,7 @@ func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte,
|
|||
|
||||
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
||||
ts := int64(info.Timestamp * 1000)
|
||||
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, content, ts)
|
||||
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, &MessageContent{content, intent.IsCustomPuppet}, ts)
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to handle message %s: %v", info.Id, err)
|
||||
return
|
||||
|
@ -905,7 +912,7 @@ func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte,
|
|||
|
||||
portal.bridge.Formatter.ParseWhatsApp(captionContent)
|
||||
|
||||
_, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, captionContent, ts)
|
||||
_, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, &MessageContent{captionContent, intent.IsCustomPuppet}, ts)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to handle caption of message %s: %v", info.Id, err)
|
||||
}
|
||||
|
@ -1198,7 +1205,7 @@ func (portal *Portal) Cleanup(puppetsOnly bool) {
|
|||
}
|
||||
puppet := portal.bridge.GetPuppetByMXID(member)
|
||||
if puppet != nil {
|
||||
_, err = puppet.Intent().LeaveRoom(portal.MXID)
|
||||
_, err = puppet.DefaultIntent().LeaveRoom(portal.MXID)
|
||||
if err != nil {
|
||||
portal.log.Errorln("Error leaving as puppet while cleaning up portal:", err)
|
||||
}
|
||||
|
|
58
puppet.go
58
puppet.go
|
@ -71,20 +71,49 @@ func (bridge *Bridge) GetPuppetByJID(jid types.WhatsAppID) *Puppet {
|
|||
}
|
||||
puppet = bridge.NewPuppet(dbPuppet)
|
||||
bridge.puppets[puppet.JID] = puppet
|
||||
if len(puppet.CustomMXID) > 0 {
|
||||
bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet
|
||||
}
|
||||
}
|
||||
return puppet
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAllPuppets() []*Puppet {
|
||||
func (bridge *Bridge) GetPuppetByCustomMXID(mxid types.MatrixUserID) *Puppet {
|
||||
bridge.puppetsLock.Lock()
|
||||
defer bridge.puppetsLock.Unlock()
|
||||
puppet, ok := bridge.puppetsByCustomMXID[mxid]
|
||||
if !ok {
|
||||
dbPuppet := bridge.DB.Puppet.GetByCustomMXID(mxid)
|
||||
if dbPuppet == nil {
|
||||
return nil
|
||||
}
|
||||
puppet = bridge.NewPuppet(dbPuppet)
|
||||
bridge.puppets[puppet.JID] = puppet
|
||||
bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet
|
||||
}
|
||||
return puppet
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAllPuppetsWithCustomMXID() []*Puppet {
|
||||
return bridge.dbPuppetsToPuppets(bridge.DB.Puppet.GetAllWithCustomMXID())
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAllPuppets() []*Puppet {
|
||||
return bridge.dbPuppetsToPuppets(bridge.DB.Puppet.GetAll())
|
||||
}
|
||||
|
||||
func (bridge *Bridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Puppet {
|
||||
bridge.puppetsLock.Lock()
|
||||
defer bridge.puppetsLock.Unlock()
|
||||
dbPuppets := bridge.DB.Puppet.GetAll()
|
||||
output := make([]*Puppet, len(dbPuppets))
|
||||
for index, dbPuppet := range dbPuppets {
|
||||
puppet, ok := bridge.puppets[dbPuppet.JID]
|
||||
if !ok {
|
||||
puppet = bridge.NewPuppet(dbPuppet)
|
||||
bridge.puppets[dbPuppet.JID] = puppet
|
||||
if len(dbPuppet.CustomMXID) > 0 {
|
||||
bridge.puppetsByCustomMXID[dbPuppet.CustomMXID] = puppet
|
||||
}
|
||||
}
|
||||
output[index] = puppet
|
||||
}
|
||||
|
@ -116,13 +145,26 @@ type Puppet struct {
|
|||
typingAt int64
|
||||
|
||||
MXID types.MatrixUserID
|
||||
|
||||
customIntent *appservice.IntentAPI
|
||||
}
|
||||
|
||||
func (puppet *Puppet) PhoneNumber() string {
|
||||
return strings.Replace(puppet.JID, whatsappExt.NewUserSuffix, "", 1)
|
||||
}
|
||||
|
||||
func (puppet *Puppet) Intent() *appservice.IntentAPI {
|
||||
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
|
||||
if puppet.customIntent == nil || portal.Key.JID == puppet.JID{
|
||||
return puppet.DefaultIntent()
|
||||
}
|
||||
return puppet.customIntent
|
||||
}
|
||||
|
||||
func (puppet *Puppet) CustomIntent() *appservice.IntentAPI {
|
||||
return puppet.customIntent
|
||||
}
|
||||
|
||||
func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
|
||||
return puppet.bridge.AS.Intent(puppet.MXID)
|
||||
}
|
||||
|
||||
|
@ -145,7 +187,7 @@ func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicI
|
|||
}
|
||||
|
||||
if len(avatar.URL) == 0 {
|
||||
err := puppet.Intent().SetAvatarURL("")
|
||||
err := puppet.DefaultIntent().SetAvatarURL("")
|
||||
if err != nil {
|
||||
puppet.log.Warnln("Failed to remove avatar:", err)
|
||||
}
|
||||
|
@ -160,13 +202,13 @@ func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicI
|
|||
}
|
||||
|
||||
mime := http.DetectContentType(data)
|
||||
resp, err := puppet.Intent().UploadBytes(data, mime)
|
||||
resp, err := puppet.DefaultIntent().UploadBytes(data, mime)
|
||||
if err != nil {
|
||||
puppet.log.Warnln("Failed to upload avatar:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = puppet.Intent().SetAvatarURL(resp.ContentURI)
|
||||
err = puppet.DefaultIntent().SetAvatarURL(resp.ContentURI)
|
||||
if err != nil {
|
||||
puppet.log.Warnln("Failed to set avatar:", err)
|
||||
}
|
||||
|
@ -175,7 +217,7 @@ func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicI
|
|||
}
|
||||
|
||||
func (puppet *Puppet) Sync(source *User, contact whatsapp.Contact) {
|
||||
err := puppet.Intent().EnsureRegistered()
|
||||
err := puppet.DefaultIntent().EnsureRegistered()
|
||||
if err != nil {
|
||||
puppet.log.Errorln("Failed to ensure registered:", err)
|
||||
}
|
||||
|
@ -185,7 +227,7 @@ func (puppet *Puppet) Sync(source *User, contact whatsapp.Contact) {
|
|||
}
|
||||
newName, quality := puppet.bridge.Config.Bridge.FormatDisplayname(contact)
|
||||
if puppet.Displayname != newName && quality >= puppet.NameQuality {
|
||||
err := puppet.Intent().SetDisplayName(newName)
|
||||
err := puppet.DefaultIntent().SetDisplayName(newName)
|
||||
if err == nil {
|
||||
puppet.Displayname = newName
|
||||
puppet.NameQuality = quality
|
||||
|
|
13
user.go
13
user.go
|
@ -465,14 +465,15 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
|
|||
puppet := user.bridge.GetPuppetByJID(info.SenderJID)
|
||||
switch info.Status {
|
||||
case whatsappExt.PresenceUnavailable:
|
||||
puppet.Intent().SetPresence("offline")
|
||||
_ = puppet.DefaultIntent().SetPresence("offline")
|
||||
case whatsappExt.PresenceAvailable:
|
||||
if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
||||
puppet.Intent().UserTyping(puppet.typingIn, false, 0)
|
||||
portal := user.bridge.GetPortalByMXID(puppet.typingIn)
|
||||
_, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
||||
puppet.typingIn = ""
|
||||
puppet.typingAt = 0
|
||||
} else {
|
||||
puppet.Intent().SetPresence("online")
|
||||
_ = puppet.DefaultIntent().SetPresence("online")
|
||||
}
|
||||
case whatsappExt.PresenceComposing:
|
||||
portal := user.GetPortalByJID(info.JID)
|
||||
|
@ -480,11 +481,11 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
|
|||
if puppet.typingIn == portal.MXID {
|
||||
return
|
||||
}
|
||||
puppet.Intent().UserTyping(puppet.typingIn, false, 0)
|
||||
_, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
||||
}
|
||||
puppet.typingIn = portal.MXID
|
||||
puppet.typingAt = time.Now().Unix()
|
||||
puppet.Intent().UserTyping(portal.MXID, true, 15*1000)
|
||||
_, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,7 +497,7 @@ func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
|
|||
}
|
||||
|
||||
go func() {
|
||||
intent := user.bridge.GetPuppetByJID(info.SenderJID).Intent()
|
||||
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
|
||||
for _, id := range info.IDs {
|
||||
msg := user.bridge.DB.Message.GetByJID(portal.Key, id)
|
||||
if msg == nil {
|
||||
|
|
Loading…
Reference in a new issue