forked from MirrorHub/mautrix-whatsapp
Fix and add things
* Fix user ID reservation in registration * Fix some database things * Add commands * Add basic contact syncing and portal creation * Add better error logging
This commit is contained in:
parent
edd4f817e4
commit
a9124b89bd
13 changed files with 455 additions and 98 deletions
104
commands.go
Normal file
104
commands.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
||||||
|
// Copyright (C) 2018 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 (
|
||||||
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"strings"
|
||||||
|
"maunium.net/go/mautrix-appservice"
|
||||||
|
"maunium.net/go/maulogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandHandler struct {
|
||||||
|
bridge *Bridge
|
||||||
|
log maulogger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandHandler(bridge *Bridge) *CommandHandler {
|
||||||
|
return &CommandHandler{
|
||||||
|
bridge: bridge,
|
||||||
|
log: bridge.Log.Sub("Command handler"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandEvent struct {
|
||||||
|
Bot *appservice.IntentAPI
|
||||||
|
Bridge *Bridge
|
||||||
|
Handler *CommandHandler
|
||||||
|
RoomID types.MatrixRoomID
|
||||||
|
User *User
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ce *CommandEvent) Reply(msg string) {
|
||||||
|
_, err := ce.Bot.SendNotice(string(ce.RoomID), msg)
|
||||||
|
if err != nil {
|
||||||
|
ce.Handler.log.Warnfln("Failed to reply to command from %s: %v", ce.User.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, message string) {
|
||||||
|
args := strings.Split(message, " ")
|
||||||
|
cmd := strings.ToLower(args[0])
|
||||||
|
ce := &CommandEvent{
|
||||||
|
Bot: handler.bridge.AppService.BotIntent(),
|
||||||
|
Bridge: handler.bridge,
|
||||||
|
Handler: handler,
|
||||||
|
RoomID: roomID,
|
||||||
|
User: user,
|
||||||
|
Args: args[1:],
|
||||||
|
}
|
||||||
|
switch cmd {
|
||||||
|
case "login":
|
||||||
|
handler.CommandLogin(ce)
|
||||||
|
case "logout":
|
||||||
|
handler.CommandLogout(ce)
|
||||||
|
case "help":
|
||||||
|
handler.CommandHelp(ce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandLogin(ce *CommandEvent) {
|
||||||
|
if ce.User.Session != nil {
|
||||||
|
ce.Reply("You're already logged in.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ce.User.Connect(true)
|
||||||
|
ce.User.Login(ce.RoomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandLogout(ce *CommandEvent) {
|
||||||
|
if ce.User.Session == nil {
|
||||||
|
ce.Reply("You're not logged in.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := ce.User.Conn.Logout()
|
||||||
|
if err != nil {
|
||||||
|
ce.User.log.Warnln("Error while logging out:", err)
|
||||||
|
ce.Reply("Error while logging out (see logs for details)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ce.User.Conn = nil
|
||||||
|
ce.User.Session = nil
|
||||||
|
ce.User.Update()
|
||||||
|
ce.Reply("Logged out successfully.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandHelp(ce *CommandEvent) {
|
||||||
|
ce.Reply("Help is not yet implemented 3:")
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
"strings"
|
"strings"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"github.com/Rhymen/go-whatsapp"
|
||||||
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BridgeConfig struct {
|
type BridgeConfig struct {
|
||||||
|
@ -62,16 +64,16 @@ type UsernameTemplateArgs struct {
|
||||||
UserID string
|
UserID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BridgeConfig) FormatDisplayname(displayname string) string {
|
func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
bc.displaynameTemplate.Execute(&buf, DisplaynameTemplateArgs{
|
bc.displaynameTemplate.Execute(&buf, contact)
|
||||||
Displayname: displayname,
|
|
||||||
})
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BridgeConfig) FormatUsername(receiver, userID string) string {
|
func (bc BridgeConfig) FormatUsername(receiver types.MatrixUserID, userID types.WhatsAppID) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
receiver = strings.Replace(receiver, "@", "=40", 1)
|
||||||
|
receiver = strings.Replace(receiver, ":", "=3", 1)
|
||||||
bc.usernameTemplate.Execute(&buf, UsernameTemplateArgs{
|
bc.usernameTemplate.Execute(&buf, UsernameTemplateArgs{
|
||||||
Receiver: receiver,
|
Receiver: receiver,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
@ -80,7 +82,12 @@ func (bc BridgeConfig) FormatUsername(receiver, userID string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BridgeConfig) MarshalYAML() (interface{}, error) {
|
func (bc BridgeConfig) MarshalYAML() (interface{}, error) {
|
||||||
bc.DisplaynameTemplate = bc.FormatDisplayname("{{.Displayname}}")
|
bc.DisplaynameTemplate = bc.FormatDisplayname(whatsapp.Contact{
|
||||||
|
Jid: "{{.Jid}}",
|
||||||
|
Notify: "{{.Notify}}",
|
||||||
|
Name: "{{.Name}}",
|
||||||
|
Short: "{{.Short}}",
|
||||||
|
})
|
||||||
bc.UsernameTemplate = bc.FormatUsername("{{.Receiver}}", "{{.UserID}}")
|
bc.UsernameTemplate = bc.FormatUsername("{{.Receiver}}", "{{.UserID}}")
|
||||||
return bc, nil
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (config *Config) copyToRegistration(registration *appservice.Registration)
|
||||||
registration.SenderLocalpart = config.AppService.Bot.Username
|
registration.SenderLocalpart = config.AppService.Bot.Username
|
||||||
|
|
||||||
userIDRegex, err := regexp.Compile(fmt.Sprintf("^@%s:%s$",
|
userIDRegex, err := regexp.Compile(fmt.Sprintf("^@%s:%s$",
|
||||||
config.Bridge.FormatUsername("[0-9]+", "[0-9]+"),
|
config.Bridge.FormatUsername(".+", "[0-9]+"),
|
||||||
config.Homeserver.Domain))
|
config.Homeserver.Domain))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -56,10 +56,20 @@ func New(file string) (*Database, error) {
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) CreateTables() {
|
func (db *Database) CreateTables() error {
|
||||||
db.User.CreateTable()
|
err := db.User.CreateTable()
|
||||||
db.Portal.CreateTable()
|
if err != nil {
|
||||||
db.Puppet.CreateTable()
|
return err
|
||||||
|
}
|
||||||
|
err = db.Portal.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.Puppet.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scannable interface {
|
type Scannable interface {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package database
|
||||||
import (
|
import (
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PortalQuery struct {
|
type PortalQuery struct {
|
||||||
|
@ -33,7 +34,7 @@ func (pq *PortalQuery) CreateTable() error {
|
||||||
mxid VARCHAR(255) NOT NULL UNIQUE,
|
mxid VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
|
||||||
PRIMARY KEY (jid, owner),
|
PRIMARY KEY (jid, owner),
|
||||||
FOREIGN KEY owner REFERENCES user(mxid)
|
FOREIGN KEY (owner) REFERENCES user(mxid)
|
||||||
)`)
|
)`)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -80,22 +81,34 @@ type Portal struct {
|
||||||
JID types.WhatsAppID
|
JID types.WhatsAppID
|
||||||
MXID types.MatrixRoomID
|
MXID types.MatrixRoomID
|
||||||
Owner types.MatrixUserID
|
Owner types.MatrixUserID
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Avatar string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) Scan(row Scannable) *Portal {
|
func (portal *Portal) Scan(row Scannable) *Portal {
|
||||||
err := row.Scan(&portal.JID, &portal.MXID, &portal.Owner)
|
err := row.Scan(&portal.JID, &portal.MXID, &portal.Owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
portal.log.Fatalln("Database scan failed:", err)
|
portal.log.Fatalln("Database scan failed:", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return portal
|
return portal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) Insert() error {
|
func (portal *Portal) Insert() error {
|
||||||
_, err := portal.db.Exec("INSERT INTO portal VALUES (?, ?, ?)", portal.JID, portal.Owner, portal.MXID)
|
_, err := portal.db.Exec("INSERT INTO portal VALUES (?, ?, ?)", portal.JID, portal.Owner, portal.MXID)
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Warnfln("Failed to update %s->%s: %v", portal.JID, portal.Owner, err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) Update() error {
|
func (portal *Portal) Update() error {
|
||||||
_, err := portal.db.Exec("UPDATE portal SET mxid=? WHERE jid=? AND owner=?", portal.MXID, portal.JID, portal.Owner)
|
_, err := portal.db.Exec("UPDATE portal SET mxid=? WHERE jid=? AND owner=?", portal.MXID, portal.JID, portal.Owner)
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Warnfln("Failed to update %s->%s: %v", portal.JID, portal.Owner, err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package database
|
||||||
import (
|
import (
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PuppetQuery struct {
|
type PuppetQuery struct {
|
||||||
|
@ -59,7 +60,7 @@ func (pq *PuppetQuery) GetAll(receiver types.MatrixUserID) (puppets []*Puppet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pq *PuppetQuery) Get(jid types.WhatsAppID, receiver types.MatrixUserID) *Puppet {
|
func (pq *PuppetQuery) Get(jid types.WhatsAppID, receiver types.MatrixUserID) *Puppet {
|
||||||
row := pq.db.QueryRow("SELECT * FROM user WHERE jid=? AND receiver=?", jid, receiver)
|
row := pq.db.QueryRow("SELECT * FROM puppet WHERE jid=? AND receiver=?", jid, receiver)
|
||||||
if row == nil {
|
if row == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -80,14 +81,20 @@ type Puppet struct {
|
||||||
func (puppet *Puppet) Scan(row Scannable) *Puppet {
|
func (puppet *Puppet) Scan(row Scannable) *Puppet {
|
||||||
err := row.Scan(&puppet.JID, &puppet.Receiver, &puppet.Displayname, &puppet.Avatar)
|
err := row.Scan(&puppet.JID, &puppet.Receiver, &puppet.Displayname, &puppet.Avatar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
puppet.log.Fatalln("Database scan failed:", err)
|
puppet.log.Fatalln("Database scan failed:", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return puppet
|
return puppet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (puppet *Puppet) Insert() error {
|
func (puppet *Puppet) Insert() error {
|
||||||
_, err := puppet.db.Exec("INSERT INTO puppet VALUES (?, ?, ?, ?)",
|
_, err := puppet.db.Exec("INSERT INTO puppet VALUES (?, ?, ?, ?)",
|
||||||
puppet.JID, puppet.Receiver, puppet.Displayname, puppet.Avatar)
|
puppet.JID, puppet.Receiver, puppet.Displayname, puppet.Avatar)
|
||||||
|
if err != nil {
|
||||||
|
puppet.log.Errorln("Failed to insert %s->%s: %v", puppet.JID, puppet.Receiver, err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,5 +102,8 @@ func (puppet *Puppet) Update() error {
|
||||||
_, err := puppet.db.Exec("UPDATE puppet SET displayname=?, avatar=? WHERE jid=? AND receiver=?",
|
_, err := puppet.db.Exec("UPDATE puppet SET displayname=?, avatar=? WHERE jid=? AND receiver=?",
|
||||||
puppet.Displayname, puppet.Avatar,
|
puppet.Displayname, puppet.Avatar,
|
||||||
puppet.JID, puppet.Receiver)
|
puppet.JID, puppet.Receiver)
|
||||||
|
if err != nil {
|
||||||
|
puppet.log.Errorln("Failed to update %s->%s: %v", puppet.JID, puppet.Receiver, err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserQuery struct {
|
type UserQuery struct {
|
||||||
|
@ -74,18 +75,21 @@ type User struct {
|
||||||
db *Database
|
db *Database
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
||||||
UserID types.MatrixUserID
|
ID types.MatrixUserID
|
||||||
ManagementRoom types.MatrixRoomID
|
ManagementRoom types.MatrixRoomID
|
||||||
Session *whatsapp.Session
|
Session *whatsapp.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Scan(row Scannable) *User {
|
func (user *User) Scan(row Scannable) *User {
|
||||||
sess := whatsapp.Session{}
|
sess := whatsapp.Session{}
|
||||||
err := row.Scan(&user.UserID, &user.ManagementRoom, &sess.ClientId, &sess.ClientToken, &sess.ServerToken,
|
err := row.Scan(&user.ID, &user.ManagementRoom, &sess.ClientId, &sess.ClientToken, &sess.ServerToken,
|
||||||
&sess.EncKey, &sess.MacKey, &sess.Wid)
|
&sess.EncKey, &sess.MacKey, &sess.Wid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
user.log.Fatalln("Database scan failed:", err)
|
user.log.Fatalln("Database scan failed:", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if len(sess.ClientId) > 0 {
|
if len(sess.ClientId) > 0 {
|
||||||
user.Session = &sess
|
user.Session = &sess
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,7 +103,7 @@ func (user *User) Insert() error {
|
||||||
if user.Session != nil {
|
if user.Session != nil {
|
||||||
sess = *user.Session
|
sess = *user.Session
|
||||||
}
|
}
|
||||||
_, err := user.db.Exec("INSERT INTO user VALUES (?, ?, ?, ?, ?, ?, ?, ?)", user.UserID, user.ManagementRoom,
|
_, err := user.db.Exec("INSERT INTO user VALUES (?, ?, ?, ?, ?, ?, ?, ?)", user.ID, user.ManagementRoom,
|
||||||
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid)
|
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -111,6 +115,6 @@ func (user *User) Update() error {
|
||||||
}
|
}
|
||||||
_, err := user.db.Exec("UPDATE user SET management_room=?, client_id=?, client_token=?, server_token=?, enc_key=?, mac_key=?, wid=? WHERE mxid=?",
|
_, err := user.db.Exec("UPDATE user SET management_room=?, client_id=?, client_token=?, server_token=?, enc_key=?, mac_key=?, wid=? WHERE mxid=?",
|
||||||
user.ManagementRoom,
|
user.ManagementRoom,
|
||||||
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid, user.UserID)
|
sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid, user.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,12 +43,14 @@ appservice:
|
||||||
# Bridge config. Currently unused.
|
# Bridge config. Currently unused.
|
||||||
bridge:
|
bridge:
|
||||||
# Localpart template of MXIDs for WhatsApp users.
|
# Localpart template of MXIDs for WhatsApp users.
|
||||||
# {{.receiver}} is replaced with the WhatsApp user ID of the Matrix user receiving messages.
|
# {{.Receiver}} is replaced with the WhatsApp user ID of the Matrix user receiving messages.
|
||||||
# {{.userid}} is replaced with the user ID of the WhatsApp user.
|
# {{.UserID}} is replaced with the user ID of the WhatsApp user.
|
||||||
username_template: "whatsapp_{{.Receiver}}_{{.UserID}}"
|
username_template: "whatsapp_{{.Receiver}}_{{.UserID}}"
|
||||||
# Displayname template for WhatsApp users.
|
# Displayname template for WhatsApp users.
|
||||||
# {{.displayname}} is replaced with the display name of the WhatsApp user.
|
# {{.Name}} - display name
|
||||||
displayname_template: "{{.Displayname}}"
|
# {{.Short}} - short display name (usually first name)
|
||||||
|
# {{.Notify}} - nickname (set by the target WhatsApp user)
|
||||||
|
displayname_template: "{{if .Name}}{{.Name}}{{else if .Notify}}{{.Notify}}{{else if .Short}}{{.Short}}{{else}}Unnamed user{{end}}"
|
||||||
|
|
||||||
# The prefix for commands. Only required in non-management rooms.
|
# The prefix for commands. Only required in non-management rooms.
|
||||||
command_prefix: "!wa"
|
command_prefix: "!wa"
|
||||||
|
|
25
main.go
25
main.go
|
@ -26,7 +26,6 @@ import (
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,6 +59,7 @@ func (bridge *Bridge) GenerateRegistration() {
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
AppService *appservice.AppService
|
AppService *appservice.AppService
|
||||||
EventProcessor *appservice.EventProcessor
|
EventProcessor *appservice.EventProcessor
|
||||||
|
MatrixHandler *MatrixHandler
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
DB *database.Database
|
DB *database.Database
|
||||||
Log log.Logger
|
Log log.Logger
|
||||||
|
@ -67,11 +67,13 @@ type Bridge struct {
|
||||||
StateStore *AutosavingStateStore
|
StateStore *AutosavingStateStore
|
||||||
|
|
||||||
users map[types.MatrixUserID]*User
|
users map[types.MatrixUserID]*User
|
||||||
|
managementRooms map[types.MatrixRoomID]*User
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBridge() *Bridge {
|
func NewBridge() *Bridge {
|
||||||
bridge := &Bridge{
|
bridge := &Bridge{
|
||||||
users: make(map[types.MatrixUserID]*User),
|
users: make(map[types.MatrixUserID]*User),
|
||||||
|
managementRooms: make(map[types.MatrixRoomID]*User),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
bridge.Config, err = config.Load(*configPath)
|
bridge.Config, err = config.Load(*configPath)
|
||||||
|
@ -111,23 +113,28 @@ func (bridge *Bridge) Init() {
|
||||||
os.Exit(13)
|
os.Exit(13)
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.Log.Debugln("Initializing event processor")
|
bridge.Log.Debugln("Initializing Matrix event processor")
|
||||||
bridge.EventProcessor = appservice.NewEventProcessor(bridge.AppService)
|
bridge.EventProcessor = appservice.NewEventProcessor(bridge.AppService)
|
||||||
bridge.EventProcessor.On(gomatrix.EventMessage, bridge.HandleMessage)
|
bridge.Log.Debugln("Initializing Matrix event handler")
|
||||||
bridge.EventProcessor.On(gomatrix.StateMember, bridge.HandleMembership)
|
bridge.MatrixHandler = NewMatrixHandler(bridge)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) Start() {
|
func (bridge *Bridge) Start() {
|
||||||
bridge.DB.CreateTables()
|
err := bridge.DB.CreateTables()
|
||||||
|
if err != nil {
|
||||||
|
bridge.Log.Fatalln("Failed to create database tables:", err)
|
||||||
|
os.Exit(14)
|
||||||
|
}
|
||||||
bridge.Log.Debugln("Starting application service HTTP server")
|
bridge.Log.Debugln("Starting application service HTTP server")
|
||||||
go bridge.AppService.Start()
|
go bridge.AppService.Start()
|
||||||
bridge.Log.Debugln("Starting event processor")
|
bridge.Log.Debugln("Starting event processor")
|
||||||
go bridge.EventProcessor.Start()
|
go bridge.EventProcessor.Start()
|
||||||
bridge.Log.Debugln("Updating bot profile")
|
|
||||||
go bridge.UpdateBotProfile()
|
go bridge.UpdateBotProfile()
|
||||||
|
go bridge.StartUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) UpdateBotProfile() {
|
func (bridge *Bridge) UpdateBotProfile() {
|
||||||
|
bridge.Log.Debugln("Updating bot profile")
|
||||||
botConfig := bridge.Config.AppService.Bot
|
botConfig := bridge.Config.AppService.Bot
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -150,6 +157,12 @@ func (bridge *Bridge) UpdateBotProfile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) StartUsers() {
|
||||||
|
for _, user := range bridge.GetAllUsers() {
|
||||||
|
go user.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) Stop() {
|
func (bridge *Bridge) Stop() {
|
||||||
bridge.AppService.Stop()
|
bridge.AppService.Stop()
|
||||||
bridge.EventProcessor.Stop()
|
bridge.EventProcessor.Stop()
|
||||||
|
|
75
matrix.go
75
matrix.go
|
@ -18,26 +18,49 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"maunium.net/go/mautrix-appservice"
|
||||||
|
"maunium.net/go/maulogger"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) HandleBotInvite(evt *gomatrix.Event) {
|
type MatrixHandler struct {
|
||||||
intent := bridge.AppService.BotIntent()
|
bridge *Bridge
|
||||||
|
as *appservice.AppService
|
||||||
|
log maulogger.Logger
|
||||||
|
cmd *CommandHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatrixHandler(bridge *Bridge) *MatrixHandler {
|
||||||
|
handler := &MatrixHandler{
|
||||||
|
bridge: bridge,
|
||||||
|
as: bridge.AppService,
|
||||||
|
log: bridge.Log.Sub("Matrix"),
|
||||||
|
cmd: NewCommandHandler(bridge),
|
||||||
|
}
|
||||||
|
bridge.EventProcessor.On(gomatrix.EventMessage, handler.HandleMessage)
|
||||||
|
bridge.EventProcessor.On(gomatrix.StateMember, handler.HandleMembership)
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mx *MatrixHandler) HandleBotInvite(evt *gomatrix.Event) {
|
||||||
|
intent := mx.as.BotIntent()
|
||||||
|
|
||||||
resp, err := intent.JoinRoom(evt.RoomID, "", nil)
|
resp, err := intent.JoinRoom(evt.RoomID, "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bridge.Log.Debugln("Failed to join room", evt.RoomID, "with invite from", evt.Sender)
|
mx.log.Debugln("Failed to join room", evt.RoomID, "with invite from", evt.Sender)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
members, err := intent.JoinedMembers(resp.RoomID)
|
members, err := intent.JoinedMembers(resp.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bridge.Log.Debugln("Failed to get members in room", resp.RoomID, "after accepting invite from", evt.Sender)
|
mx.log.Debugln("Failed to get members in room", resp.RoomID, "after accepting invite from", evt.Sender)
|
||||||
intent.LeaveRoom(resp.RoomID)
|
intent.LeaveRoom(resp.RoomID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(members.Joined) < 2 {
|
if len(members.Joined) < 2 {
|
||||||
bridge.Log.Debugln("Leaving empty room", resp.RoomID, "after accepting invite from", evt.Sender)
|
mx.log.Debugln("Leaving empty room", resp.RoomID, "after accepting invite from", evt.Sender)
|
||||||
intent.LeaveRoom(resp.RoomID)
|
intent.LeaveRoom(resp.RoomID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -46,31 +69,49 @@ func (bridge *Bridge) HandleBotInvite(evt *gomatrix.Event) {
|
||||||
for mxid, _ := range members.Joined {
|
for mxid, _ := range members.Joined {
|
||||||
if mxid == intent.UserID || mxid == evt.Sender {
|
if mxid == intent.UserID || mxid == evt.Sender {
|
||||||
continue
|
continue
|
||||||
} else if _, _, ok := bridge.ParsePuppetMXID(mxid); ok {
|
} else if _, _, ok := mx.bridge.ParsePuppetMXID(types.MatrixUserID(mxid)); ok {
|
||||||
hasPuppets = true
|
hasPuppets = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bridge.Log.Debugln("Leaving multi-user room", resp.RoomID, "after accepting invite from", evt.Sender)
|
mx.log.Debugln("Leaving multi-user room", resp.RoomID, "after accepting invite from", evt.Sender)
|
||||||
intent.SendNotice(resp.RoomID, "This bridge is user-specific, please don't invite me into rooms with other users.")
|
intent.SendNotice(resp.RoomID, "This bridge is user-specific, please don't invite me into rooms with other users.")
|
||||||
intent.LeaveRoom(resp.RoomID)
|
intent.LeaveRoom(resp.RoomID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasPuppets {
|
if !hasPuppets {
|
||||||
user := bridge.GetUser(evt.Sender)
|
user := mx.bridge.GetUser(types.MatrixUserID(evt.Sender))
|
||||||
user.ManagementRoom = resp.RoomID
|
user.SetManagementRoom(types.MatrixRoomID(resp.RoomID))
|
||||||
user.Update()
|
intent.SendNotice(string(user.ManagementRoom), "This room has been registered as your bridge management/status room.")
|
||||||
intent.SendNotice(user.ManagementRoom, "This room has been registered as your bridge management/status room.")
|
mx.log.Debugln(resp.RoomID, "registered as a management room with", evt.Sender)
|
||||||
bridge.Log.Debugln(resp.RoomID, "registered as a management room with", evt.Sender)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) HandleMembership(evt *gomatrix.Event) {
|
func (mx *MatrixHandler) HandleMembership(evt *gomatrix.Event) {
|
||||||
bridge.Log.Debugln(evt.Content, evt.Content.Membership, evt.GetStateKey())
|
mx.log.Debugln(evt.Content, evt.Content.Membership, evt.GetStateKey())
|
||||||
if evt.Content.Membership == "invite" && evt.GetStateKey() == bridge.AppService.BotMXID() {
|
if evt.Content.Membership == "invite" && evt.GetStateKey() == mx.as.BotMXID() {
|
||||||
bridge.HandleBotInvite(evt)
|
mx.HandleBotInvite(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) HandleMessage(evt *gomatrix.Event) {
|
func (mx *MatrixHandler) HandleMessage(evt *gomatrix.Event) {
|
||||||
|
roomID := types.MatrixRoomID(evt.RoomID)
|
||||||
|
user := mx.bridge.GetUser(types.MatrixUserID(evt.Sender))
|
||||||
|
|
||||||
|
if evt.Content.MsgType == gomatrix.MsgText {
|
||||||
|
commandPrefix := mx.bridge.Config.Bridge.CommandPrefix
|
||||||
|
hasCommandPrefix := strings.HasPrefix(evt.Content.Body, commandPrefix)
|
||||||
|
if hasCommandPrefix {
|
||||||
|
evt.Content.Body = strings.TrimLeft(evt.Content.Body[len(commandPrefix):], " ")
|
||||||
|
}
|
||||||
|
if hasCommandPrefix || roomID == user.ManagementRoom {
|
||||||
|
mx.cmd.Handle(roomID, user, evt.Content.Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
portal := user.GetPortalByMXID(roomID)
|
||||||
|
if portal != nil {
|
||||||
|
portal.HandleMessage(evt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
59
portal.go
59
portal.go
|
@ -21,13 +21,16 @@ import (
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"maunium.net/go/gomatrix"
|
||||||
|
"strings"
|
||||||
|
"maunium.net/go/mautrix-appservice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (user *User) GetPortalByMXID(mxid types.MatrixRoomID) *Portal {
|
func (user *User) GetPortalByMXID(mxid types.MatrixRoomID) *Portal {
|
||||||
portal, ok := user.portalsByMXID[mxid]
|
portal, ok := user.portalsByMXID[mxid]
|
||||||
if !ok {
|
if !ok {
|
||||||
dbPortal := user.bridge.DB.Portal.GetByMXID(mxid)
|
dbPortal := user.bridge.DB.Portal.GetByMXID(mxid)
|
||||||
if dbPortal == nil || dbPortal.Owner != user.UserID {
|
if dbPortal == nil || dbPortal.Owner != user.ID {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
portal = user.NewPortal(dbPortal)
|
portal = user.NewPortal(dbPortal)
|
||||||
|
@ -42,9 +45,12 @@ func (user *User) GetPortalByMXID(mxid types.MatrixRoomID) *Portal {
|
||||||
func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal {
|
func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal {
|
||||||
portal, ok := user.portalsByJID[jid]
|
portal, ok := user.portalsByJID[jid]
|
||||||
if !ok {
|
if !ok {
|
||||||
dbPortal := user.bridge.DB.Portal.GetByJID(user.UserID, jid)
|
dbPortal := user.bridge.DB.Portal.GetByJID(user.ID, jid)
|
||||||
if dbPortal == nil {
|
if dbPortal == nil {
|
||||||
return nil
|
dbPortal = user.bridge.DB.Portal.New()
|
||||||
|
dbPortal.JID = jid
|
||||||
|
dbPortal.Owner = user.ID
|
||||||
|
dbPortal.Insert()
|
||||||
}
|
}
|
||||||
portal = user.NewPortal(dbPortal)
|
portal = user.NewPortal(dbPortal)
|
||||||
user.portalsByJID[portal.JID] = portal
|
user.portalsByJID[portal.JID] = portal
|
||||||
|
@ -56,7 +62,7 @@ func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) GetAllPortals() []*Portal {
|
func (user *User) GetAllPortals() []*Portal {
|
||||||
dbPortals := user.bridge.DB.Portal.GetAll(user.UserID)
|
dbPortals := user.bridge.DB.Portal.GetAll(user.ID)
|
||||||
output := make([]*Portal, len(dbPortals))
|
output := make([]*Portal, len(dbPortals))
|
||||||
for index, dbPortal := range dbPortals {
|
for index, dbPortal := range dbPortals {
|
||||||
portal, ok := user.portalsByJID[dbPortal.JID]
|
portal, ok := user.portalsByJID[dbPortal.JID]
|
||||||
|
@ -88,3 +94,48 @@ type Portal struct {
|
||||||
bridge *Bridge
|
bridge *Bridge
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) CreateMatrixRoom() error {
|
||||||
|
if len(portal.MXID) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := portal.Name
|
||||||
|
topic := ""
|
||||||
|
isPrivateChat := false
|
||||||
|
if strings.HasSuffix(portal.JID, "s.whatsapp.net") {
|
||||||
|
puppet := portal.user.GetPuppetByJID(portal.JID)
|
||||||
|
name = puppet.Displayname
|
||||||
|
topic = "WhatsApp private chat"
|
||||||
|
isPrivateChat = true
|
||||||
|
}
|
||||||
|
resp, err := portal.MainIntent().CreateRoom(&gomatrix.ReqCreateRoom{
|
||||||
|
Visibility: "private",
|
||||||
|
Name: name,
|
||||||
|
Topic: topic,
|
||||||
|
Invite: []string{portal.user.ID},
|
||||||
|
Preset: "private_chat",
|
||||||
|
IsDirect: isPrivateChat,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
portal.MXID = resp.RoomID
|
||||||
|
portal.Update()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) IsPrivateChat() bool {
|
||||||
|
return strings.HasSuffix(portal.JID, puppetJIDStrippedSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) MainIntent() *appservice.IntentAPI {
|
||||||
|
if portal.IsPrivateChat() {
|
||||||
|
return portal.user.GetPuppetByJID(portal.JID).Intent()
|
||||||
|
}
|
||||||
|
return portal.bridge.AppService.BotIntent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) HandleMessage(evt *gomatrix.Event) {
|
||||||
|
portal.log.Debugln("Received event:", evt)
|
||||||
|
}
|
||||||
|
|
33
puppet.go
33
puppet.go
|
@ -23,8 +23,11 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
"strings"
|
"strings"
|
||||||
|
"maunium.net/go/mautrix-appservice"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const puppetJIDStrippedSuffix = "@s.whatsapp.net"
|
||||||
|
|
||||||
func (bridge *Bridge) ParsePuppetMXID(mxid types.MatrixUserID) (types.MatrixUserID, types.WhatsAppID, bool) {
|
func (bridge *Bridge) ParsePuppetMXID(mxid types.MatrixUserID) (types.MatrixUserID, types.WhatsAppID, bool) {
|
||||||
userIDRegex, err := regexp.Compile(fmt.Sprintf("^@%s:%s$",
|
userIDRegex, err := regexp.Compile(fmt.Sprintf("^@%s:%s$",
|
||||||
bridge.Config.Bridge.FormatUsername("([0-9]+)", "([0-9]+)"),
|
bridge.Config.Bridge.FormatUsername("([0-9]+)", "([0-9]+)"),
|
||||||
|
@ -38,11 +41,12 @@ func (bridge *Bridge) ParsePuppetMXID(mxid types.MatrixUserID) (types.MatrixUser
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := match[1]
|
receiver := types.MatrixUserID(match[1])
|
||||||
receiver = strings.Replace(receiver, "=40", "@", 1)
|
receiver = strings.Replace(receiver, "=40", "@", 1)
|
||||||
colonIndex := strings.LastIndex(receiver, "=3")
|
colonIndex := strings.LastIndex(receiver, "=3")
|
||||||
receiver = receiver[:colonIndex] + ":" + receiver[colonIndex+len("=3"):]
|
receiver = receiver[:colonIndex] + ":" + receiver[colonIndex+len("=3"):]
|
||||||
return types.MatrixUserID(receiver), types.WhatsAppID(match[2]), true
|
jid := types.WhatsAppID(match[2] + puppetJIDStrippedSuffix)
|
||||||
|
return receiver, jid, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
func (bridge *Bridge) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
||||||
|
@ -61,7 +65,7 @@ func (bridge *Bridge) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
||||||
|
|
||||||
func (user *User) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
func (user *User) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
||||||
receiver, jid, ok := user.bridge.ParsePuppetMXID(mxid)
|
receiver, jid, ok := user.bridge.ParsePuppetMXID(mxid)
|
||||||
if !ok || receiver != user.UserID {
|
if !ok || receiver != user.ID {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,9 +75,12 @@ func (user *User) GetPuppetByMXID(mxid types.MatrixUserID) *Puppet {
|
||||||
func (user *User) GetPuppetByJID(jid types.WhatsAppID) *Puppet {
|
func (user *User) GetPuppetByJID(jid types.WhatsAppID) *Puppet {
|
||||||
puppet, ok := user.puppets[jid]
|
puppet, ok := user.puppets[jid]
|
||||||
if !ok {
|
if !ok {
|
||||||
dbPuppet := user.bridge.DB.Puppet.Get(jid, user.UserID)
|
dbPuppet := user.bridge.DB.Puppet.Get(jid, user.ID)
|
||||||
if dbPuppet == nil {
|
if dbPuppet == nil {
|
||||||
return nil
|
dbPuppet = user.bridge.DB.Puppet.New()
|
||||||
|
dbPuppet.JID = jid
|
||||||
|
dbPuppet.Receiver = user.ID
|
||||||
|
dbPuppet.Insert()
|
||||||
}
|
}
|
||||||
puppet = user.NewPuppet(dbPuppet)
|
puppet = user.NewPuppet(dbPuppet)
|
||||||
user.puppets[puppet.JID] = puppet
|
user.puppets[puppet.JID] = puppet
|
||||||
|
@ -82,7 +89,7 @@ func (user *User) GetPuppetByJID(jid types.WhatsAppID) *Puppet {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) GetAllPuppets() []*Puppet {
|
func (user *User) GetAllPuppets() []*Puppet {
|
||||||
dbPuppets := user.bridge.DB.Puppet.GetAll(user.UserID)
|
dbPuppets := user.bridge.DB.Puppet.GetAll(user.ID)
|
||||||
output := make([]*Puppet, len(dbPuppets))
|
output := make([]*Puppet, len(dbPuppets))
|
||||||
for index, dbPuppet := range dbPuppets {
|
for index, dbPuppet := range dbPuppets {
|
||||||
puppet, ok := user.puppets[dbPuppet.JID]
|
puppet, ok := user.puppets[dbPuppet.JID]
|
||||||
|
@ -101,6 +108,14 @@ func (user *User) NewPuppet(dbPuppet *database.Puppet) *Puppet {
|
||||||
user: user,
|
user: user,
|
||||||
bridge: user.bridge,
|
bridge: user.bridge,
|
||||||
log: user.log.Sub(fmt.Sprintf("Puppet/%s", dbPuppet.JID)),
|
log: user.log.Sub(fmt.Sprintf("Puppet/%s", dbPuppet.JID)),
|
||||||
|
|
||||||
|
MXID: fmt.Sprintf("@%s:%s",
|
||||||
|
user.bridge.Config.Bridge.FormatUsername(
|
||||||
|
dbPuppet.Receiver,
|
||||||
|
strings.Replace(
|
||||||
|
dbPuppet.JID,
|
||||||
|
puppetJIDStrippedSuffix, "", 1)),
|
||||||
|
user.bridge.Config.Homeserver.Domain),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,4 +125,10 @@ type Puppet struct {
|
||||||
user *User
|
user *User
|
||||||
bridge *Bridge
|
bridge *Bridge
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
||||||
|
MXID types.MatrixUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (puppet *Puppet) Intent() *appservice.IntentAPI {
|
||||||
|
return puppet.bridge.AppService.Intent(puppet.MXID)
|
||||||
}
|
}
|
||||||
|
|
147
user.go
147
user.go
|
@ -20,11 +20,11 @@ import (
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
"time"
|
"time"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
log "maunium.net/go/maulogger"
|
log "maunium.net/go/maulogger"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
"strings"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -45,10 +45,14 @@ func (bridge *Bridge) GetUser(userID types.MatrixUserID) *User {
|
||||||
dbUser := bridge.DB.User.Get(userID)
|
dbUser := bridge.DB.User.Get(userID)
|
||||||
if dbUser == nil {
|
if dbUser == nil {
|
||||||
dbUser = bridge.DB.User.New()
|
dbUser = bridge.DB.User.New()
|
||||||
|
dbUser.ID = userID
|
||||||
dbUser.Insert()
|
dbUser.Insert()
|
||||||
}
|
}
|
||||||
user = bridge.NewUser(dbUser)
|
user = bridge.NewUser(dbUser)
|
||||||
bridge.users[user.UserID] = user
|
bridge.users[user.ID] = user
|
||||||
|
if len(user.ManagementRoom) > 0 {
|
||||||
|
bridge.managementRooms[user.ManagementRoom] = user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
@ -57,57 +61,87 @@ func (bridge *Bridge) GetAllUsers() []*User {
|
||||||
dbUsers := bridge.DB.User.GetAll()
|
dbUsers := bridge.DB.User.GetAll()
|
||||||
output := make([]*User, len(dbUsers))
|
output := make([]*User, len(dbUsers))
|
||||||
for index, dbUser := range dbUsers {
|
for index, dbUser := range dbUsers {
|
||||||
user, ok := bridge.users[dbUser.UserID]
|
user, ok := bridge.users[dbUser.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
user = bridge.NewUser(dbUser)
|
user = bridge.NewUser(dbUser)
|
||||||
bridge.users[user.UserID] = user
|
bridge.users[user.ID] = user
|
||||||
|
if len(user.ManagementRoom) > 0 {
|
||||||
|
bridge.managementRooms[user.ManagementRoom] = user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
output[index] = user
|
output[index] = user
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) InitWhatsApp() {
|
|
||||||
users := bridge.GetAllUsers()
|
|
||||||
for _, user := range users {
|
|
||||||
user.Connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) NewUser(dbUser *database.User) *User {
|
func (bridge *Bridge) NewUser(dbUser *database.User) *User {
|
||||||
return &User{
|
return &User{
|
||||||
User: dbUser,
|
User: dbUser,
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
log: bridge.Log.Sub("User").Sub(dbUser.UserID),
|
log: bridge.Log.Sub("User").Sub(string(dbUser.ID)),
|
||||||
|
portalsByMXID: make(map[types.MatrixRoomID]*Portal),
|
||||||
|
portalsByJID: make(map[types.WhatsAppID]*Portal),
|
||||||
|
puppets: make(map[types.WhatsAppID]*Puppet),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Connect() {
|
func (user *User) SetManagementRoom(roomID types.MatrixRoomID) {
|
||||||
|
existingUser, ok := user.bridge.managementRooms[roomID]
|
||||||
|
if ok {
|
||||||
|
existingUser.ManagementRoom = ""
|
||||||
|
existingUser.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
user.ManagementRoom = roomID
|
||||||
|
user.bridge.managementRooms[user.ManagementRoom] = user
|
||||||
|
user.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) SetSession(session *whatsapp.Session) {
|
||||||
|
user.Session = session
|
||||||
|
user.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) Start() {
|
||||||
|
if user.Connect(false) {
|
||||||
|
user.Sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) Connect(evenIfNoSession bool) bool {
|
||||||
|
if user.Conn != nil {
|
||||||
|
return true
|
||||||
|
} else if !evenIfNoSession && user.Session == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
user.log.Debugln("Connecting to WhatsApp")
|
||||||
var err error
|
var err error
|
||||||
user.Conn, err = whatsapp.NewConn(20 * time.Second)
|
user.Conn, err = whatsapp.NewConn(20 * time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Errorln("Failed to connect to WhatsApp:", err)
|
user.log.Errorln("Failed to connect to WhatsApp:", err)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
user.log.Debugln("WhatsApp connection successful")
|
||||||
user.Conn.AddHandler(user)
|
user.Conn.AddHandler(user)
|
||||||
user.RestoreSession()
|
return user.RestoreSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) RestoreSession() {
|
func (user *User) RestoreSession() bool {
|
||||||
if user.Session != nil {
|
if user.Session != nil {
|
||||||
sess, err := user.Conn.RestoreSession(*user.Session)
|
sess, err := user.Conn.RestoreSession(*user.Session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Errorln("Failed to restore session:", err)
|
user.log.Errorln("Failed to restore session:", err)
|
||||||
user.Session = nil
|
//user.SetSession(nil)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
user.Session = &sess
|
user.SetSession(&sess)
|
||||||
user.log.Debugln("Session restored")
|
user.log.Debugln("Session restored successfully")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Login(roomID string) {
|
func (user *User) Login(roomID types.MatrixRoomID) {
|
||||||
bot := user.bridge.AppService.BotClient()
|
bot := user.bridge.AppService.BotClient()
|
||||||
|
|
||||||
qrChan := make(chan string, 2)
|
qrChan := make(chan string, 2)
|
||||||
|
@ -130,7 +164,7 @@ func (user *User) Login(roomID string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.SendImage(roomID, string(qrCode), resp.ContentURI)
|
bot.SendImage(roomID, string(code), resp.ContentURI)
|
||||||
}()
|
}()
|
||||||
session, err := user.Conn.Login(qrChan)
|
session, err := user.Conn.Login(qrChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -145,32 +179,79 @@ func (user *User) Login(roomID string) {
|
||||||
go user.Sync()
|
go user.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) Sync() {
|
func (user *User) SyncPuppet(contact whatsapp.Contact) {
|
||||||
chats, err := user.Conn.Chats()
|
puppet := user.GetPuppetByJID(contact.Jid)
|
||||||
|
puppet.Intent().EnsureRegistered()
|
||||||
|
|
||||||
|
newName := user.bridge.Config.Bridge.FormatDisplayname(contact)
|
||||||
|
puppet.log.Debugln(puppet.Displayname, newName, contact.Name)
|
||||||
|
if puppet.Displayname != newName {
|
||||||
|
puppet.Displayname = newName
|
||||||
|
puppet.Update()
|
||||||
|
puppet.Intent().SetDisplayName(puppet.Displayname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) SyncPortal(contact whatsapp.Contact) {
|
||||||
|
portal := user.GetPortalByJID(contact.Jid)
|
||||||
|
|
||||||
|
if len(portal.MXID) == 0 {
|
||||||
|
if !portal.IsPrivateChat() {
|
||||||
|
portal.Name = contact.Name
|
||||||
|
}
|
||||||
|
err := portal.CreateMatrixRoom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Warnln("Failed to get chats")
|
user.log.Errorln("Failed to create portal:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.log.Debugln(chats)
|
}
|
||||||
|
|
||||||
|
if !portal.IsPrivateChat() && portal.Name != contact.Name {
|
||||||
|
portal.Name = contact.Name
|
||||||
|
portal.Update()
|
||||||
|
// TODO add SetRoomName function to intent API
|
||||||
|
portal.MainIntent().SendStateEvent(portal.MXID, "m.room.name", "", map[string]interface{}{
|
||||||
|
"name": portal.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) Sync() {
|
||||||
|
user.log.Debugln("Syncing...")
|
||||||
|
user.Conn.Contacts()
|
||||||
|
user.log.Debugln(user.Conn.Store.Contacts)
|
||||||
|
for jid, contact := range user.Conn.Store.Contacts {
|
||||||
|
dat, _ := json.Marshal(&contact)
|
||||||
|
user.log.Debugln(string(dat))
|
||||||
|
if strings.HasSuffix(jid, puppetJIDStrippedSuffix) {
|
||||||
|
user.SyncPuppet(contact)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contact.Notify) == 0 && !strings.HasSuffix(jid, "@g.us") {
|
||||||
|
// Don't bridge yet
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
user.SyncPortal(contact)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleError(err error) {
|
func (user *User) HandleError(err error) {
|
||||||
user.log.Errorln("WhatsApp error:", err)
|
user.log.Errorln("WhatsApp error:", err)
|
||||||
fmt.Fprintf(os.Stderr, "%v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
|
func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
|
||||||
fmt.Println(message)
|
user.log.Debugln("Text message:", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
|
func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
|
||||||
fmt.Println(message)
|
user.log.Debugln("Image message:", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
|
func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||||
fmt.Println(message)
|
user.log.Debugln("Video message:", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleJsonMessage(message string) {
|
func (user *User) HandleJsonMessage(message string) {
|
||||||
fmt.Println(message)
|
user.log.Debugln("JSON message:", message)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue