forked from MirrorHub/mautrix-whatsapp
Add basic relaybot support. Fixes #20
This commit is contained in:
parent
e2d9e2fc57
commit
03d42640fe
14 changed files with 356 additions and 89 deletions
46
commands.go
46
commands.go
|
@ -20,13 +20,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/mautrix/format"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
|
|
||||||
"maunium.net/go/maulogger/v2"
|
"maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
|
"maunium.net/go/mautrix/format"
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
@ -53,6 +53,7 @@ type CommandEvent struct {
|
||||||
Handler *CommandHandler
|
Handler *CommandHandler
|
||||||
RoomID types.MatrixRoomID
|
RoomID types.MatrixRoomID
|
||||||
User *User
|
User *User
|
||||||
|
Command string
|
||||||
Args []string
|
Args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +61,11 @@ type CommandEvent struct {
|
||||||
func (ce *CommandEvent) Reply(msg string, args ...interface{}) {
|
func (ce *CommandEvent) Reply(msg string, args ...interface{}) {
|
||||||
content := format.RenderMarkdown(fmt.Sprintf(msg, args...))
|
content := format.RenderMarkdown(fmt.Sprintf(msg, args...))
|
||||||
content.MsgType = mautrix.MsgNotice
|
content.MsgType = mautrix.MsgNotice
|
||||||
_, err := ce.Bot.SendMessageEvent(ce.User.ManagementRoom, mautrix.EventMessage, content)
|
room := ce.User.ManagementRoom
|
||||||
|
if len(room) == 0 {
|
||||||
|
room = ce.RoomID
|
||||||
|
}
|
||||||
|
_, err := ce.Bot.SendMessageEvent(room, mautrix.EventMessage, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ce.Handler.log.Warnfln("Failed to reply to command from %s: %v", ce.User.MXID, err)
|
ce.Handler.log.Warnfln("Failed to reply to command from %s: %v", ce.User.MXID, err)
|
||||||
}
|
}
|
||||||
|
@ -69,17 +74,27 @@ func (ce *CommandEvent) Reply(msg string, args ...interface{}) {
|
||||||
// Handle handles messages to the bridge
|
// Handle handles messages to the bridge
|
||||||
func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, message string) {
|
func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, message string) {
|
||||||
args := strings.Split(message, " ")
|
args := strings.Split(message, " ")
|
||||||
cmd := strings.ToLower(args[0])
|
|
||||||
ce := &CommandEvent{
|
ce := &CommandEvent{
|
||||||
Bot: handler.bridge.Bot,
|
Bot: handler.bridge.Bot,
|
||||||
Bridge: handler.bridge,
|
Bridge: handler.bridge,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
User: user,
|
User: user,
|
||||||
|
Command: strings.ToLower(args[0]),
|
||||||
Args: args[1:],
|
Args: args[1:],
|
||||||
}
|
}
|
||||||
handler.log.Debugfln("%s sent '%s' in %s", user.MXID, message, roomID)
|
handler.log.Debugfln("%s sent '%s' in %s", user.MXID, message, roomID)
|
||||||
switch cmd {
|
if roomID == handler.bridge.Config.Bridge.Relaybot.ManagementRoom {
|
||||||
|
handler.CommandRelaybot(ce)
|
||||||
|
} else {
|
||||||
|
handler.CommandMux(ce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
|
||||||
|
switch ce.Command {
|
||||||
|
case "relaybot":
|
||||||
|
handler.CommandRelaybot(ce)
|
||||||
case "login":
|
case "login":
|
||||||
handler.CommandLogin(ce)
|
handler.CommandLogin(ce)
|
||||||
case "logout-matrix":
|
case "logout-matrix":
|
||||||
|
@ -111,7 +126,7 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cmd {
|
switch ce.Command {
|
||||||
case "login-matrix":
|
case "login-matrix":
|
||||||
handler.CommandLoginMatrix(ce)
|
handler.CommandLoginMatrix(ce)
|
||||||
case "logout":
|
case "logout":
|
||||||
|
@ -130,6 +145,21 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandRelaybot(ce *CommandEvent) {
|
||||||
|
if handler.bridge.Relaybot == nil {
|
||||||
|
ce.Reply("The relaybot is disabled")
|
||||||
|
} else if !ce.User.Admin {
|
||||||
|
ce.Reply("Only admins can manage the relaybot")
|
||||||
|
} else {
|
||||||
|
if ce.Command == "relaybot" {
|
||||||
|
ce.Command = strings.ToLower(ce.Args[0])
|
||||||
|
ce.Args = ce.Args[1:]
|
||||||
|
}
|
||||||
|
ce.User = handler.bridge.Relaybot
|
||||||
|
handler.CommandMux(ce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (handler *CommandHandler) CommandDevTest(ce *CommandEvent) {
|
func (handler *CommandHandler) CommandDevTest(ce *CommandEvent) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -305,7 +335,7 @@ const cmdHelpHelp = `help - Prints this help`
|
||||||
// CommandHelp handles help command
|
// CommandHelp handles help command
|
||||||
func (handler *CommandHandler) CommandHelp(ce *CommandEvent) {
|
func (handler *CommandHandler) CommandHelp(ce *CommandEvent) {
|
||||||
cmdPrefix := ""
|
cmdPrefix := ""
|
||||||
if ce.User.ManagementRoom != ce.RoomID {
|
if ce.User.ManagementRoom != ce.RoomID || ce.User.IsRelaybot {
|
||||||
cmdPrefix = handler.bridge.Config.Bridge.CommandPrefix + " "
|
cmdPrefix = handler.bridge.Config.Bridge.CommandPrefix + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
@ -64,6 +65,8 @@ type BridgeConfig struct {
|
||||||
|
|
||||||
Permissions PermissionConfig `yaml:"permissions"`
|
Permissions PermissionConfig `yaml:"permissions"`
|
||||||
|
|
||||||
|
Relaybot RelaybotConfig `yaml:"relaybot"`
|
||||||
|
|
||||||
usernameTemplate *template.Template `yaml:"-"`
|
usernameTemplate *template.Template `yaml:"-"`
|
||||||
displaynameTemplate *template.Template `yaml:"-"`
|
displaynameTemplate *template.Template `yaml:"-"`
|
||||||
communityTemplate *template.Template `yaml:"-"`
|
communityTemplate *template.Template `yaml:"-"`
|
||||||
|
@ -172,6 +175,7 @@ type PermissionLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PermissionLevelDefault PermissionLevel = 0
|
PermissionLevelDefault PermissionLevel = 0
|
||||||
|
PermissionLevelRelaybot PermissionLevel = 5
|
||||||
PermissionLevelUser PermissionLevel = 10
|
PermissionLevelUser PermissionLevel = 10
|
||||||
PermissionLevelAdmin PermissionLevel = 100
|
PermissionLevelAdmin PermissionLevel = 100
|
||||||
)
|
)
|
||||||
|
@ -188,6 +192,8 @@ func (pc *PermissionConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||||
}
|
}
|
||||||
for key, value := range rawPC {
|
for key, value := range rawPC {
|
||||||
switch strings.ToLower(value) {
|
switch strings.ToLower(value) {
|
||||||
|
case "relaybot":
|
||||||
|
(*pc)[key] = PermissionLevelRelaybot
|
||||||
case "user":
|
case "user":
|
||||||
(*pc)[key] = PermissionLevelUser
|
(*pc)[key] = PermissionLevelUser
|
||||||
case "admin":
|
case "admin":
|
||||||
|
@ -211,6 +217,8 @@ func (pc *PermissionConfig) MarshalYAML() (interface{}, error) {
|
||||||
rawPC := make(map[string]string)
|
rawPC := make(map[string]string)
|
||||||
for key, value := range *pc {
|
for key, value := range *pc {
|
||||||
switch value {
|
switch value {
|
||||||
|
case PermissionLevelRelaybot:
|
||||||
|
rawPC[key] = "relaybot"
|
||||||
case PermissionLevelUser:
|
case PermissionLevelUser:
|
||||||
rawPC[key] = "user"
|
rawPC[key] = "user"
|
||||||
case PermissionLevelAdmin:
|
case PermissionLevelAdmin:
|
||||||
|
@ -222,12 +230,16 @@ func (pc *PermissionConfig) MarshalYAML() (interface{}, error) {
|
||||||
return rawPC, nil
|
return rawPC, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc PermissionConfig) IsRelaybotWhitelisted(userID string) bool {
|
||||||
|
return pc.GetPermissionLevel(userID) >= PermissionLevelRelaybot
|
||||||
|
}
|
||||||
|
|
||||||
func (pc PermissionConfig) IsWhitelisted(userID string) bool {
|
func (pc PermissionConfig) IsWhitelisted(userID string) bool {
|
||||||
return pc.GetPermissionLevel(userID) >= 10
|
return pc.GetPermissionLevel(userID) >= PermissionLevelUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PermissionConfig) IsAdmin(userID string) bool {
|
func (pc PermissionConfig) IsAdmin(userID string) bool {
|
||||||
return pc.GetPermissionLevel(userID) >= 100
|
return pc.GetPermissionLevel(userID) >= PermissionLevelAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc PermissionConfig) GetPermissionLevel(userID string) PermissionLevel {
|
func (pc PermissionConfig) GetPermissionLevel(userID string) PermissionLevel {
|
||||||
|
@ -249,3 +261,55 @@ func (pc PermissionConfig) GetPermissionLevel(userID string) PermissionLevel {
|
||||||
|
|
||||||
return PermissionLevelDefault
|
return PermissionLevelDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RelaybotConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
ManagementRoom string `yaml:"management"`
|
||||||
|
InviteUsers []types.MatrixUserID `yaml:"invites"`
|
||||||
|
|
||||||
|
MessageFormats map[mautrix.MessageType]string `yaml:"message_formats"`
|
||||||
|
messageTemplates *template.Template `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type umRelaybotConfig RelaybotConfig
|
||||||
|
|
||||||
|
func (rc *RelaybotConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
err := unmarshal((*umRelaybotConfig)(rc))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.messageTemplates = template.New("messageTemplates")
|
||||||
|
for key, format := range rc.MessageFormats {
|
||||||
|
_, err := rc.messageTemplates.New(string(key)).Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sender struct {
|
||||||
|
UserID types.MatrixUserID
|
||||||
|
mautrix.Member
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatData struct {
|
||||||
|
Sender Sender
|
||||||
|
Message string
|
||||||
|
Content mautrix.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RelaybotConfig) FormatMessage(evt *mautrix.Event, member mautrix.Member) (string, error) {
|
||||||
|
var output strings.Builder
|
||||||
|
err := rc.messageTemplates.ExecuteTemplate(&output, string(evt.Content.MsgType), formatData{
|
||||||
|
Sender: Sender{
|
||||||
|
UserID: evt.Sender,
|
||||||
|
Member: member,
|
||||||
|
},
|
||||||
|
Content: evt.Content,
|
||||||
|
Message: evt.Content.FormattedBody,
|
||||||
|
})
|
||||||
|
return output.String(), err
|
||||||
|
}
|
||||||
|
|
|
@ -70,23 +70,23 @@ func (store *SQLStateStore) MarkRegistered(userID string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLStateStore) GetRoomMemberships(roomID string) map[string]mautrix.Membership {
|
func (store *SQLStateStore) GetRoomMembers(roomID string) map[string]mautrix.Member {
|
||||||
memberships := make(map[string]mautrix.Membership)
|
members := make(map[string]mautrix.Member)
|
||||||
rows, err := store.db.Query("SELECT user_id, membership FROM mx_user_profile WHERE room_id=$1", roomID)
|
rows, err := store.db.Query("SELECT user_id, membership, displayname, avatar_url FROM mx_user_profile WHERE room_id=$1", roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return memberships
|
return members
|
||||||
}
|
}
|
||||||
var userID string
|
var userID string
|
||||||
var membership mautrix.Membership
|
var member mautrix.Member
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err := rows.Scan(&userID, &membership)
|
err := rows.Scan(&userID, &member.Membership, &member.Displayname, &member.AvatarURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
store.log.Warnfln("Failed to scan membership in %s: %v", roomID, err)
|
store.log.Warnfln("Failed to scan member in %s: %v", roomID, err)
|
||||||
} else {
|
} else {
|
||||||
memberships[userID] = membership
|
members[userID] = member
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return memberships
|
return members
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLStateStore) GetMembership(roomID, userID string) mautrix.Membership {
|
func (store *SQLStateStore) GetMembership(roomID, userID string) mautrix.Membership {
|
||||||
|
@ -99,6 +99,24 @@ func (store *SQLStateStore) GetMembership(roomID, userID string) mautrix.Members
|
||||||
return membership
|
return membership
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *SQLStateStore) GetMember(roomID, userID string) mautrix.Member {
|
||||||
|
member, ok := store.TryGetMember(roomID, userID)
|
||||||
|
if !ok {
|
||||||
|
member.Membership = mautrix.MembershipLeave
|
||||||
|
}
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *SQLStateStore) TryGetMember(roomID, userID string) (mautrix.Member, bool) {
|
||||||
|
row := store.db.QueryRow("SELECT membership, displayname, avatar_url FROM mx_user_profile WHERE room_id=$1 AND user_id=$2", roomID, userID)
|
||||||
|
var member mautrix.Member
|
||||||
|
err := row.Scan(&member.Membership, &member.Displayname, &member.AvatarURL)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
store.log.Warnfln("Failed to scan member info of %s in %s: %v", userID, roomID, err)
|
||||||
|
}
|
||||||
|
return member, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (store *SQLStateStore) IsInRoom(roomID, userID string) bool {
|
func (store *SQLStateStore) IsInRoom(roomID, userID string) bool {
|
||||||
return store.IsMembership(roomID, userID, "join")
|
return store.IsMembership(roomID, userID, "join")
|
||||||
}
|
}
|
||||||
|
@ -116,6 +134,7 @@ func (store *SQLStateStore) IsMembership(roomID, userID string, allowedMembershi
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *SQLStateStore) SetMembership(roomID, userID string, membership mautrix.Membership) {
|
func (store *SQLStateStore) SetMembership(roomID, userID string, membership mautrix.Membership) {
|
||||||
var err error
|
var err error
|
||||||
if store.db.dialect == "postgres" {
|
if store.db.dialect == "postgres" {
|
||||||
|
@ -131,6 +150,22 @@ func (store *SQLStateStore) SetMembership(roomID, userID string, membership maut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *SQLStateStore) SetMember(roomID, userID string, member mautrix.Member) {
|
||||||
|
var err error
|
||||||
|
if store.db.dialect == "postgres" {
|
||||||
|
_, err = store.db.Exec(`INSERT INTO mx_user_profile (room_id, user_id, membership, displayname, avatar_url) VALUES ($1, $2, $3, $4, $5)
|
||||||
|
ON CONFLICT (room_id, user_id) DO UPDATE SET membership=$3`, roomID, userID, member.Membership, member.Displayname, member.AvatarURL)
|
||||||
|
} else if store.db.dialect == "sqlite3" {
|
||||||
|
_, err = store.db.Exec("INSERT OR REPLACE INTO mx_user_profile (room_id, user_id, membership, displayname, avatar_url) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
roomID, userID, member.Membership, member.Displayname, member.AvatarURL)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unsupported dialect %s", store.db.dialect)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
store.log.Warnfln("Failed to set membership of %s in %s to %s: %v", userID, roomID, member, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (store *SQLStateStore) SetPowerLevels(roomID string, levels *mautrix.PowerLevels) {
|
func (store *SQLStateStore) SetPowerLevels(roomID string, levels *mautrix.PowerLevels) {
|
||||||
levelsBytes, err := json.Marshal(levels)
|
levelsBytes, err := json.Marshal(levels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -47,7 +47,7 @@ func init() {
|
||||||
return executeBatch(tx, valueStrings, values...)
|
return executeBatch(tx, valueStrings, values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
migrateMemberships := func(tx *sql.Tx, rooms map[string]map[string]mautrix.Membership) error {
|
migrateMemberships := func(tx *sql.Tx, rooms map[string]map[string]mautrix.Member) error {
|
||||||
for roomID, members := range rooms {
|
for roomID, members := range rooms {
|
||||||
if len(members) == 0 {
|
if len(members) == 0 {
|
||||||
continue
|
continue
|
||||||
|
@ -125,7 +125,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
} else if err = migrateRegistrations(tx, store.Registrations); err != nil {
|
} else if err = migrateRegistrations(tx, store.Registrations); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = migrateMemberships(tx, store.Memberships); err != nil {
|
} else if err = migrateMemberships(tx, store.Members); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = migratePowerLevels(tx, store.PowerLevels); err != nil {
|
} else if err = migratePowerLevels(tx, store.PowerLevels); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
16
database/upgrades/2019-11-10-full-member-state-store.go
Normal file
16
database/upgrades/2019-11-10-full-member-state-store.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package upgrades
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
upgrades[10] = upgrade{"Add columns to store full member info in state store", func(tx *sql.Tx, ctx context) error {
|
||||||
|
_, err := tx.Exec(`ALTER TABLE mx_user_profile ADD COLUMN displayname TEXT`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = tx.Exec(`ALTER TABLE mx_user_profile ADD COLUMN avatar_url VARCHAR(255)`)
|
||||||
|
return err
|
||||||
|
}}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ type upgrade struct {
|
||||||
fn upgradeFunc
|
fn upgradeFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberOfUpgrades = 10
|
const NumberOfUpgrades = 11
|
||||||
|
|
||||||
var upgrades [NumberOfUpgrades]upgrade
|
var upgrades [NumberOfUpgrades]upgrade
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,13 @@ func (user *User) SetPortalKeys(newKeys []PortalKeyWithMeta) error {
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) IsInPortal(jid types.WhatsAppID) bool {
|
||||||
|
row := user.db.QueryRow(`SELECT portal_jid, portal_receiver FROM user_portal WHERE user_jid=$1 AND portal_jid=$2 AND (portal_receiver=$1 OR portal_receiver=$2)`, user.jidPtr(), &jid)
|
||||||
|
var scanJid, scanReceiver types.WhatsAppID
|
||||||
|
_ = row.Scan(&scanJid, &scanReceiver)
|
||||||
|
return scanJid == jid && (scanReceiver == jid || scanReceiver == user.JID)
|
||||||
|
}
|
||||||
|
|
||||||
func (user *User) GetPortalKeys() []PortalKey {
|
func (user *User) GetPortalKeys() []PortalKey {
|
||||||
rows, err := user.db.Query(`SELECT portal_jid, portal_receiver FROM user_portal WHERE user_jid=$1`, user.jidPtr())
|
rows, err := user.db.Query(`SELECT portal_jid, portal_receiver FROM user_portal WHERE user_jid=$1`, user.jidPtr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# Homeserver details.
|
# Homeserver details.
|
||||||
homeserver:
|
homeserver:
|
||||||
# The address that this appservice can use to connect to the homeserver.
|
# The address that this appservice can use to connect to the homeserver.
|
||||||
address: https://matrix.org
|
address: https://example.com
|
||||||
# The domain of the homeserver (for MXIDs, etc).
|
# The domain of the homeserver (for MXIDs, etc).
|
||||||
domain: matrix.org
|
domain: example.com
|
||||||
|
|
||||||
# Application service host/registration related details.
|
# Application service host/registration related details.
|
||||||
# Changing these values requires regeneration of the registration.
|
# Changing these values requires regeneration of the registration.
|
||||||
|
@ -122,6 +122,7 @@ bridge:
|
||||||
|
|
||||||
# Permissions for using the bridge.
|
# Permissions for using the bridge.
|
||||||
# Permitted values:
|
# Permitted values:
|
||||||
|
# relaybot - Talk through the relaybot (if enabled), no access otherwise
|
||||||
# user - Access to use the bridge to chat with a WhatsApp account.
|
# user - Access to use the bridge to chat with a WhatsApp account.
|
||||||
# admin - User level and some additional administration tools
|
# admin - User level and some additional administration tools
|
||||||
# Permitted keys:
|
# Permitted keys:
|
||||||
|
@ -129,9 +130,30 @@ bridge:
|
||||||
# domain - All users on that homeserver
|
# domain - All users on that homeserver
|
||||||
# mxid - Specific user
|
# mxid - Specific user
|
||||||
permissions:
|
permissions:
|
||||||
|
"*": relaybot
|
||||||
"example.com": user
|
"example.com": user
|
||||||
"@admin:example.com": admin
|
"@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 config.
|
# Logging config.
|
||||||
logging:
|
logging:
|
||||||
# The directory for log files. Will be created if not found.
|
# The directory for log files. Will be created if not found.
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -12,11 +12,11 @@ require (
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
maunium.net/go/mauflag v1.0.0
|
maunium.net/go/mauflag v1.0.0
|
||||||
maunium.net/go/maulogger/v2 v2.0.0
|
maunium.net/go/maulogger/v2 v2.0.0
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190825132810-9d870654e9d2
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20191110191816-178ce1f1561d
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190901152202-40639f5932be
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20191110192030-cd699619a163
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20191004160943-faf0ee6fab98
|
github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20191109203156-c477dae1c7e9
|
||||||
gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
|
gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
|
||||||
)
|
)
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -45,8 +45,7 @@ github.com/tulir/go-whatsapp v0.0.2-0.20190830212741-33ca6ee47cf5 h1:0pUczFGOo4s
|
||||||
github.com/tulir/go-whatsapp v0.0.2-0.20190830212741-33ca6ee47cf5/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8=
|
github.com/tulir/go-whatsapp v0.0.2-0.20190830212741-33ca6ee47cf5/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8=
|
||||||
github.com/tulir/go-whatsapp v0.0.2-0.20190903182221-4e1a838ff3ba h1:exEcedSHn0qEZ1iwNwFF5brEuflhMScjFyyzmxUA+og=
|
github.com/tulir/go-whatsapp v0.0.2-0.20190903182221-4e1a838ff3ba h1:exEcedSHn0qEZ1iwNwFF5brEuflhMScjFyyzmxUA+og=
|
||||||
github.com/tulir/go-whatsapp v0.0.2-0.20190903182221-4e1a838ff3ba/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8=
|
github.com/tulir/go-whatsapp v0.0.2-0.20190903182221-4e1a838ff3ba/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8=
|
||||||
github.com/tulir/go-whatsapp v0.0.2-0.20191004160943-faf0ee6fab98 h1:TkKWIdhqxRBM8bZaJvp1q+awGJcY1f76zmlH7nHPDR8=
|
github.com/tulir/go-whatsapp v0.0.2-0.20191109203156-c477dae1c7e9/go.mod h1:ustkccVUt0hOuKikjFb6b4Eray6At5djkcKYYu4+Lco=
|
||||||
github.com/tulir/go-whatsapp v0.0.2-0.20191004160943-faf0ee6fab98/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8=
|
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -74,6 +73,7 @@ maunium.net/go/mautrix v0.1.0-alpha.3.0.20190622085722-6406f15cb8e3 h1:oVabjOi2r
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190622085722-6406f15cb8e3/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190622085722-6406f15cb8e3/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190825132810-9d870654e9d2 h1:0iVxLLAOSBqtJqhIjW9EbblMsaSYoCJRo5mHPZnytUk=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190825132810-9d870654e9d2 h1:0iVxLLAOSBqtJqhIjW9EbblMsaSYoCJRo5mHPZnytUk=
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190825132810-9d870654e9d2/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20190825132810-9d870654e9d2/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
||||||
|
maunium.net/go/mautrix v0.1.0-alpha.3.0.20191110191816-178ce1f1561d/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg=
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190618052224-6e6c9bb47548 h1:ni1nqs+2AOO+g1ND6f2W0pMcb6sIDVqzerXosO+pI2g=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190618052224-6e6c9bb47548 h1:ni1nqs+2AOO+g1ND6f2W0pMcb6sIDVqzerXosO+pI2g=
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190618052224-6e6c9bb47548/go.mod h1:yVWU0gvIHIXClgyVnShiufiDksFbFrBqHG9lDAYcmGI=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190618052224-6e6c9bb47548/go.mod h1:yVWU0gvIHIXClgyVnShiufiDksFbFrBqHG9lDAYcmGI=
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190822210104-3e49344e186b h1:/03X0PPgtk4pqXcdH86xMzOl891whG5A1hFXQ+xXons=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190822210104-3e49344e186b h1:/03X0PPgtk4pqXcdH86xMzOl891whG5A1hFXQ+xXons=
|
||||||
|
@ -86,3 +86,4 @@ maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190830063827-e7dcd7e42e7c h
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190830063827-e7dcd7e42e7c/go.mod h1:FJRRpH5+p3wCfEt6u/3kMeu9aGX/pk2PqtvjRDRW74w=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190830063827-e7dcd7e42e7c/go.mod h1:FJRRpH5+p3wCfEt6u/3kMeu9aGX/pk2PqtvjRDRW74w=
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190901152202-40639f5932be h1:sSBx9AGR4iYHRFwljqNwxXFtbY2bKLJHgI9B4whAU8I=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190901152202-40639f5932be h1:sSBx9AGR4iYHRFwljqNwxXFtbY2bKLJHgI9B4whAU8I=
|
||||||
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190901152202-40639f5932be/go.mod h1:FJRRpH5+p3wCfEt6u/3kMeu9aGX/pk2PqtvjRDRW74w=
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20190901152202-40639f5932be/go.mod h1:FJRRpH5+p3wCfEt6u/3kMeu9aGX/pk2PqtvjRDRW74w=
|
||||||
|
maunium.net/go/mautrix-appservice v0.1.0-alpha.3.0.20191110192030-cd699619a163/go.mod h1:ST7YYCoHtFC4c7/Iga8W5wwKXyxjwVh4DlsnyIU6rYw=
|
||||||
|
|
25
main.go
25
main.go
|
@ -18,23 +18,23 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
flag "maunium.net/go/mauflag"
|
flag "maunium.net/go/mauflag"
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
"maunium.net/go/mautrix-whatsapp/database/upgrades"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/config"
|
"maunium.net/go/mautrix-whatsapp/config"
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
|
"maunium.net/go/mautrix-whatsapp/database/upgrades"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
"net/http"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String()
|
var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String()
|
||||||
|
@ -104,6 +104,7 @@ type Bridge struct {
|
||||||
StateStore *database.SQLStateStore
|
StateStore *database.SQLStateStore
|
||||||
Bot *appservice.IntentAPI
|
Bot *appservice.IntentAPI
|
||||||
Formatter *Formatter
|
Formatter *Formatter
|
||||||
|
Relaybot *User
|
||||||
|
|
||||||
usersByMXID map[types.MatrixUserID]*User
|
usersByMXID map[types.MatrixUserID]*User
|
||||||
usersByJID map[types.WhatsAppID]*User
|
usersByJID map[types.WhatsAppID]*User
|
||||||
|
@ -220,6 +221,7 @@ func (bridge *Bridge) Start() {
|
||||||
bridge.Log.Fatalln("Failed to initialize database:", err)
|
bridge.Log.Fatalln("Failed to initialize database:", err)
|
||||||
os.Exit(15)
|
os.Exit(15)
|
||||||
}
|
}
|
||||||
|
bridge.LoadRelaybot()
|
||||||
bridge.Log.Debugln("Checking connection to homeserver")
|
bridge.Log.Debugln("Checking connection to homeserver")
|
||||||
bridge.ensureConnection()
|
bridge.ensureConnection()
|
||||||
bridge.Log.Debugln("Starting application service HTTP server")
|
bridge.Log.Debugln("Starting application service HTTP server")
|
||||||
|
@ -230,6 +232,21 @@ func (bridge *Bridge) Start() {
|
||||||
go bridge.StartUsers()
|
go bridge.StartUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) LoadRelaybot() {
|
||||||
|
if !bridge.Config.Bridge.Relaybot.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bridge.Relaybot = bridge.GetUserByMXID("relaybot")
|
||||||
|
if bridge.Relaybot.HasSession() {
|
||||||
|
bridge.Log.Debugln("Relaybot is enabled")
|
||||||
|
} else {
|
||||||
|
bridge.Log.Debugln("Relaybot is enabled, but not logged in")
|
||||||
|
}
|
||||||
|
bridge.Relaybot.ManagementRoom = bridge.Config.Bridge.Relaybot.ManagementRoom
|
||||||
|
bridge.Relaybot.IsRelaybot = true
|
||||||
|
bridge.Relaybot.Connect(false)
|
||||||
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) UpdateBotProfile() {
|
func (bridge *Bridge) UpdateBotProfile() {
|
||||||
bridge.Log.Debugln("Updating bot profile")
|
bridge.Log.Debugln("Updating bot profile")
|
||||||
botConfig := bridge.Config.AppService.Bot
|
botConfig := bridge.Config.AppService.Bot
|
||||||
|
|
17
matrix.go
17
matrix.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/maulogger/v2"
|
"maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
|
@ -85,6 +86,12 @@ func (mx *MatrixHandler) HandleBotInvite(evt *mautrix.Event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if evt.RoomID == mx.bridge.Config.Bridge.Relaybot.ManagementRoom {
|
||||||
|
intent.SendNotice(evt.RoomID, "This is the relaybot management room. Send `!wa help` to get a list of commands.")
|
||||||
|
mx.log.Debugln("Joined relaybot management room", evt.RoomID, "after invite from", evt.Sender)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hasPuppets := false
|
hasPuppets := false
|
||||||
for mxid, _ := range members.Joined {
|
for mxid, _ := range members.Joined {
|
||||||
if mxid == intent.UserID || mxid == evt.Sender {
|
if mxid == intent.UserID || mxid == evt.Sender {
|
||||||
|
@ -174,11 +181,11 @@ func (mx *MatrixHandler) HandleMessage(evt *mautrix.Event) {
|
||||||
roomID := types.MatrixRoomID(evt.RoomID)
|
roomID := types.MatrixRoomID(evt.RoomID)
|
||||||
user := mx.bridge.GetUserByMXID(types.MatrixUserID(evt.Sender))
|
user := mx.bridge.GetUserByMXID(types.MatrixUserID(evt.Sender))
|
||||||
|
|
||||||
if !user.Whitelisted {
|
if !user.RelaybotWhitelisted {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if evt.Content.MsgType == mautrix.MsgText {
|
if user.Whitelisted && evt.Content.MsgType == mautrix.MsgText {
|
||||||
commandPrefix := mx.bridge.Config.Bridge.CommandPrefix
|
commandPrefix := mx.bridge.Config.Bridge.CommandPrefix
|
||||||
hasCommandPrefix := strings.HasPrefix(evt.Content.Body, commandPrefix)
|
hasCommandPrefix := strings.HasPrefix(evt.Content.Body, commandPrefix)
|
||||||
if hasCommandPrefix {
|
if hasCommandPrefix {
|
||||||
|
@ -191,7 +198,7 @@ func (mx *MatrixHandler) HandleMessage(evt *mautrix.Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
portal := mx.bridge.GetPortalByMXID(roomID)
|
portal := mx.bridge.GetPortalByMXID(roomID)
|
||||||
if portal != nil {
|
if portal != nil && (user.Whitelisted || portal.HasRelaybot()) {
|
||||||
portal.HandleMatrixMessage(user, evt)
|
portal.HandleMatrixMessage(user, evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,8 +218,8 @@ func (mx *MatrixHandler) HandleRedaction(evt *mautrix.Event) {
|
||||||
if !user.HasSession() {
|
if !user.HasSession() {
|
||||||
return
|
return
|
||||||
} else if !user.IsConnected() {
|
} else if !user.IsConnected() {
|
||||||
msg := format.RenderMarkdown(fmt.Sprintf("[%[1]s](https://matrix.to/#/%[1]s): \u26a0 " +
|
msg := format.RenderMarkdown(fmt.Sprintf("[%[1]s](https://matrix.to/#/%[1]s): \u26a0 "+
|
||||||
"You are not connected to WhatsApp, so your redaction was not bridged. " +
|
"You are not connected to WhatsApp, so your redaction was not bridged. "+
|
||||||
"Use `%[2]s reconnect` to reconnect.", user.MXID, mx.bridge.Config.Bridge.CommandPrefix))
|
"Use `%[2]s reconnect` to reconnect.", user.MXID, mx.bridge.Config.Bridge.CommandPrefix))
|
||||||
msg.MsgType = mautrix.MsgNotice
|
msg.MsgType = mautrix.MsgNotice
|
||||||
_, _ = mx.bridge.Bot.SendMessageEvent(roomID, mautrix.EventMessage, msg)
|
_, _ = mx.bridge.Bot.SendMessageEvent(roomID, mautrix.EventMessage, msg)
|
||||||
|
|
142
portal.go
142
portal.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"image"
|
"image"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
|
@ -34,15 +35,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chai2010/webp"
|
"github.com/chai2010/webp"
|
||||||
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
"github.com/Rhymen/go-whatsapp"
|
||||||
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
|
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
|
||||||
"maunium.net/go/mautrix/format"
|
|
||||||
|
|
||||||
log "maunium.net/go/maulogger/v2"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix-appservice"
|
"maunium.net/go/mautrix-appservice"
|
||||||
|
"maunium.net/go/mautrix/format"
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
"maunium.net/go/mautrix-whatsapp/types"
|
"maunium.net/go/mautrix-whatsapp/types"
|
||||||
|
@ -159,6 +159,7 @@ type Portal struct {
|
||||||
messages chan PortalMessage
|
messages chan PortalMessage
|
||||||
|
|
||||||
isPrivate *bool
|
isPrivate *bool
|
||||||
|
hasRelaybot *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxMessageAgeToCreatePortal = 5 * 60 // 5 minutes
|
const MaxMessageAgeToCreatePortal = 5 * 60 // 5 minutes
|
||||||
|
@ -191,15 +192,15 @@ func (portal *Portal) handleMessage(msg PortalMessage) {
|
||||||
case whatsapp.TextMessage:
|
case whatsapp.TextMessage:
|
||||||
portal.HandleTextMessage(msg.source, data)
|
portal.HandleTextMessage(msg.source, data)
|
||||||
case whatsapp.ImageMessage:
|
case whatsapp.ImageMessage:
|
||||||
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.Type, data.Caption, false)
|
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.ContextInfo, data.Type, data.Caption, false)
|
||||||
case whatsapp.StickerMessage:
|
case whatsapp.StickerMessage:
|
||||||
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.Type, "", true)
|
portal.HandleMediaMessage(msg.source, data.Download, nil, data.Info, data.ContextInfo, data.Type, "", true)
|
||||||
case whatsapp.VideoMessage:
|
case whatsapp.VideoMessage:
|
||||||
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.Type, data.Caption, false)
|
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.ContextInfo, data.Type, data.Caption, false)
|
||||||
case whatsapp.AudioMessage:
|
case whatsapp.AudioMessage:
|
||||||
portal.HandleMediaMessage(msg.source, data.Download, nil, data.Info, data.Type, "", false)
|
portal.HandleMediaMessage(msg.source, data.Download, nil, data.Info, data.ContextInfo, data.Type, "", false)
|
||||||
case whatsapp.DocumentMessage:
|
case whatsapp.DocumentMessage:
|
||||||
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.Type, data.Title, false)
|
portal.HandleMediaMessage(msg.source, data.Download, data.Thumbnail, data.Info, data.ContextInfo, data.Type, data.Title, false)
|
||||||
case whatsappExt.MessageRevocation:
|
case whatsappExt.MessageRevocation:
|
||||||
portal.HandleMessageRevoke(msg.source, data)
|
portal.HandleMessageRevoke(msg.source, data)
|
||||||
case FakeMessage:
|
case FakeMessage:
|
||||||
|
@ -281,14 +282,7 @@ func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
|
||||||
}
|
}
|
||||||
for _, participant := range metadata.Participants {
|
for _, participant := range metadata.Participants {
|
||||||
user := portal.bridge.GetUserByJID(participant.JID)
|
user := portal.bridge.GetUserByJID(participant.JID)
|
||||||
if user != nil && !portal.bridge.AS.StateStore.IsInvited(portal.MXID, user.MXID) {
|
portal.userMXIDAction(user, portal.ensureMXIDInvited)
|
||||||
_, err = portal.MainIntent().InviteUser(portal.MXID, &mautrix.ReqInviteUser{
|
|
||||||
UserID: user.MXID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
portal.log.Warnfln("Failed to invite %s to %s: %v", user.MXID, portal.MXID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
puppet := portal.bridge.GetPuppetByJID(participant.JID)
|
puppet := portal.bridge.GetPuppetByJID(participant.JID)
|
||||||
err := puppet.IntentFor(portal).EnsureJoined(portal.MXID)
|
err := puppet.IntentFor(portal).EnsureJoined(portal.MXID)
|
||||||
|
@ -421,11 +415,30 @@ func (portal *Portal) UpdateMetadata(user *User) bool {
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) ensureUserInvited(user *User) {
|
func (portal *Portal) userMXIDAction(user *User, fn func(mxid types.MatrixUserID)) {
|
||||||
err := portal.MainIntent().EnsureInvited(portal.MXID, user.MXID)
|
if user == nil {
|
||||||
if err != nil {
|
return
|
||||||
portal.log.Warnfln("Failed to ensure %s is invited to %s: %v", user.MXID, portal.MXID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user == portal.bridge.Relaybot {
|
||||||
|
for _, mxid := range portal.bridge.Config.Bridge.Relaybot.InviteUsers {
|
||||||
|
fn(mxid)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fn(user.MXID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) ensureMXIDInvited(mxid types.MatrixUserID) {
|
||||||
|
err := portal.MainIntent().EnsureInvited(portal.MXID, mxid)
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Warnfln("Failed to ensure %s is invited to %s: %v", mxid, portal.MXID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) ensureUserInvited(user *User) {
|
||||||
|
portal.userMXIDAction(user, portal.ensureMXIDInvited)
|
||||||
|
|
||||||
customPuppet := portal.bridge.GetPuppetByCustomMXID(user.MXID)
|
customPuppet := portal.bridge.GetPuppetByCustomMXID(user.MXID)
|
||||||
if customPuppet != nil && customPuppet.CustomIntent() != nil {
|
if customPuppet != nil && customPuppet.CustomIntent() != nil {
|
||||||
_ = customPuppet.CustomIntent().EnsureJoined(portal.MXID)
|
_ = customPuppet.CustomIntent().EnsureJoined(portal.MXID)
|
||||||
|
@ -435,6 +448,11 @@ func (portal *Portal) ensureUserInvited(user *User) {
|
||||||
func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
|
func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
|
||||||
portal.log.Infoln("Syncing portal for", user.MXID)
|
portal.log.Infoln("Syncing portal for", user.MXID)
|
||||||
|
|
||||||
|
if user.IsRelaybot {
|
||||||
|
yes := true
|
||||||
|
portal.hasRelaybot = &yes
|
||||||
|
}
|
||||||
|
|
||||||
if len(portal.MXID) == 0 {
|
if len(portal.MXID) == 0 {
|
||||||
if !portal.IsPrivateChat() {
|
if !portal.IsPrivateChat() {
|
||||||
portal.Name = contact.Name
|
portal.Name = contact.Name
|
||||||
|
@ -745,11 +763,16 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invite := []string{user.MXID}
|
||||||
|
if user.IsRelaybot {
|
||||||
|
invite = portal.bridge.Config.Bridge.Relaybot.InviteUsers
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := intent.CreateRoom(&mautrix.ReqCreateRoom{
|
resp, err := intent.CreateRoom(&mautrix.ReqCreateRoom{
|
||||||
Visibility: "private",
|
Visibility: "private",
|
||||||
Name: portal.Name,
|
Name: portal.Name,
|
||||||
Topic: portal.Topic,
|
Topic: portal.Topic,
|
||||||
Invite: []string{user.MXID},
|
Invite: invite,
|
||||||
Preset: "private_chat",
|
Preset: "private_chat",
|
||||||
IsDirect: isPrivateChat,
|
IsDirect: isPrivateChat,
|
||||||
InitialState: initialState,
|
InitialState: initialState,
|
||||||
|
@ -783,6 +806,16 @@ func (portal *Portal) IsPrivateChat() bool {
|
||||||
return *portal.isPrivate
|
return *portal.isPrivate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) HasRelaybot() bool {
|
||||||
|
if portal.bridge.Relaybot == nil {
|
||||||
|
return false
|
||||||
|
} else if portal.hasRelaybot == nil {
|
||||||
|
val := portal.bridge.Relaybot.IsInPortal(portal.Key.JID)
|
||||||
|
portal.hasRelaybot = &val
|
||||||
|
}
|
||||||
|
return *portal.hasRelaybot
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) IsStatusBroadcastRoom() bool {
|
func (portal *Portal) IsStatusBroadcastRoom() bool {
|
||||||
return portal.Key.JID == "status@broadcast"
|
return portal.Key.JID == "status@broadcast"
|
||||||
}
|
}
|
||||||
|
@ -809,7 +842,7 @@ func (portal *Portal) GetMessageIntent(user *User, info whatsapp.MessageInfo) *a
|
||||||
return portal.bridge.GetPuppetByJID(info.SenderJid).IntentFor(portal)
|
return portal.bridge.GetPuppetByJID(info.SenderJid).IntentFor(portal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) SetReply(content *mautrix.Content, info whatsapp.MessageInfo) {
|
func (portal *Portal) SetReply(content *mautrix.Content, info whatsapp.ContextInfo) {
|
||||||
if len(info.QuotedMessageID) == 0 {
|
if len(info.QuotedMessageID) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -891,7 +924,7 @@ func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessa
|
||||||
}
|
}
|
||||||
|
|
||||||
portal.bridge.Formatter.ParseWhatsApp(content)
|
portal.bridge.Formatter.ParseWhatsApp(content)
|
||||||
portal.SetReply(content, message.Info)
|
portal.SetReply(content, message.ContextInfo)
|
||||||
|
|
||||||
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
||||||
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, &MessageContent{content, intent.IsCustomPuppet}, int64(message.Info.Timestamp*1000))
|
resp, err := intent.SendMassagedMessageEvent(portal.MXID, mautrix.EventMessage, &MessageContent{content, intent.IsCustomPuppet}, int64(message.Info.Timestamp*1000))
|
||||||
|
@ -902,7 +935,7 @@ func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessa
|
||||||
portal.finishHandling(source, message.Info.Source, resp.EventID)
|
portal.finishHandling(source, message.Info.Source, resp.EventID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte, error), thumbnail []byte, info whatsapp.MessageInfo, mimeType, caption string, sendAsSticker bool) {
|
func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte, error), thumbnail []byte, info whatsapp.MessageInfo, context whatsapp.ContextInfo, mimeType, caption string, sendAsSticker bool) {
|
||||||
if !portal.startHandling(info) {
|
if !portal.startHandling(info) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -967,7 +1000,7 @@ func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte,
|
||||||
MimeType: mimeType,
|
MimeType: mimeType,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
portal.SetReply(content, info)
|
portal.SetReply(content, context)
|
||||||
|
|
||||||
if thumbnail != nil {
|
if thumbnail != nil {
|
||||||
thumbnailMime := http.DetectContentType(thumbnail)
|
thumbnailMime := http.DetectContentType(thumbnail)
|
||||||
|
@ -986,7 +1019,7 @@ func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte,
|
||||||
|
|
||||||
switch strings.ToLower(strings.Split(mimeType, "/")[0]) {
|
switch strings.ToLower(strings.Split(mimeType, "/")[0]) {
|
||||||
case "image":
|
case "image":
|
||||||
if (!sendAsSticker) {
|
if !sendAsSticker {
|
||||||
content.MsgType = mautrix.MsgImage
|
content.MsgType = mautrix.MsgImage
|
||||||
}
|
}
|
||||||
cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
|
cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
|
||||||
|
@ -1070,18 +1103,15 @@ func (portal *Portal) downloadThumbnail(evt *mautrix.Event) []byte {
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) preprocessMatrixMedia(sender *User, evt *mautrix.Event, mediaType whatsapp.MediaType) *MediaUpload {
|
func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, evt *mautrix.Event, mediaType whatsapp.MediaType) *MediaUpload {
|
||||||
if evt.Content.Info == nil {
|
if evt.Content.Info == nil {
|
||||||
evt.Content.Info = &mautrix.FileInfo{}
|
evt.Content.Info = &mautrix.FileInfo{}
|
||||||
}
|
}
|
||||||
caption := evt.Content.Body
|
var caption string
|
||||||
exts, err := mime.ExtensionsByType(evt.Content.Info.MimeType)
|
if relaybotFormatted {
|
||||||
for _, ext := range exts {
|
caption = portal.bridge.Formatter.ParseMatrix(evt.Content.FormattedBody)
|
||||||
if strings.HasSuffix(caption, ext) {
|
|
||||||
caption = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := portal.MainIntent().DownloadBytes(evt.Content.URL)
|
content, err := portal.MainIntent().DownloadBytes(evt.Content.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Errorfln("Failed to download media in %s: %v", evt.ID, err)
|
portal.log.Errorfln("Failed to download media in %s: %v", evt.ID, err)
|
||||||
|
@ -1140,10 +1170,28 @@ func (portal *Portal) sendMatrixConnectionError(sender *User, eventID string) bo
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) addRelaybotFormat(user *User, evt *mautrix.Event) bool {
|
||||||
|
member := portal.MainIntent().Member(portal.MXID, evt.Sender)
|
||||||
|
if len(member.Displayname) == 0 {
|
||||||
|
member.Displayname = evt.Sender
|
||||||
|
}
|
||||||
|
|
||||||
|
if evt.Content.Format != mautrix.FormatHTML {
|
||||||
|
evt.Content.FormattedBody = strings.ReplaceAll(html.EscapeString(evt.Content.Body), "\n", "<br/>")
|
||||||
|
evt.Content.Format = mautrix.FormatHTML
|
||||||
|
}
|
||||||
|
data, err := portal.bridge.Config.Bridge.Relaybot.FormatMessage(evt, member)
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Errorln("Failed to apply relaybot format:", err)
|
||||||
|
}
|
||||||
|
evt.Content.FormattedBody = data
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
if portal.IsPrivateChat() && sender.JID != portal.Key.Receiver {
|
if !portal.HasRelaybot() && (
|
||||||
return
|
(portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) ||
|
||||||
} else if portal.sendMatrixConnectionError(sender, evt.ID) {
|
portal.sendMatrixConnectionError(sender, evt.ID)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
portal.log.Debugfln("Received event %s", evt.ID)
|
portal.log.Debugfln("Received event %s", evt.ID)
|
||||||
|
@ -1172,6 +1220,15 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
ctxInfo.QuotedMessage = msg.Content
|
ctxInfo.QuotedMessage = msg.Content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
relaybotFormatted := false
|
||||||
|
if sender.NeedsRelaybot(portal) {
|
||||||
|
if !portal.HasRelaybot() {
|
||||||
|
portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
relaybotFormatted = portal.addRelaybotFormat(sender, evt)
|
||||||
|
sender = portal.bridge.Relaybot
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
switch evt.Content.MsgType {
|
switch evt.Content.MsgType {
|
||||||
case mautrix.MsgText, mautrix.MsgEmote:
|
case mautrix.MsgText, mautrix.MsgEmote:
|
||||||
|
@ -1179,7 +1236,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
if evt.Content.Format == mautrix.FormatHTML {
|
if evt.Content.Format == mautrix.FormatHTML {
|
||||||
text = portal.bridge.Formatter.ParseMatrix(evt.Content.FormattedBody)
|
text = portal.bridge.Formatter.ParseMatrix(evt.Content.FormattedBody)
|
||||||
}
|
}
|
||||||
if evt.Content.MsgType == mautrix.MsgEmote {
|
if evt.Content.MsgType == mautrix.MsgEmote && !relaybotFormatted {
|
||||||
text = "/me " + text
|
text = "/me " + text
|
||||||
}
|
}
|
||||||
ctxInfo.MentionedJid = mentionRegex.FindAllString(text, -1)
|
ctxInfo.MentionedJid = mentionRegex.FindAllString(text, -1)
|
||||||
|
@ -1195,7 +1252,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
info.Message.Conversation = &text
|
info.Message.Conversation = &text
|
||||||
}
|
}
|
||||||
case mautrix.MsgImage:
|
case mautrix.MsgImage:
|
||||||
media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaImage)
|
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, evt, whatsapp.MediaImage)
|
||||||
if media == nil {
|
if media == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1210,7 +1267,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
FileLength: &media.FileLength,
|
FileLength: &media.FileLength,
|
||||||
}
|
}
|
||||||
case mautrix.MsgVideo:
|
case mautrix.MsgVideo:
|
||||||
media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaVideo)
|
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, evt, whatsapp.MediaVideo)
|
||||||
if media == nil {
|
if media == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1227,7 +1284,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
FileLength: &media.FileLength,
|
FileLength: &media.FileLength,
|
||||||
}
|
}
|
||||||
case mautrix.MsgAudio:
|
case mautrix.MsgAudio:
|
||||||
media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaAudio)
|
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, evt, whatsapp.MediaAudio)
|
||||||
if media == nil {
|
if media == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1242,12 +1299,13 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *mautrix.Event) {
|
||||||
FileLength: &media.FileLength,
|
FileLength: &media.FileLength,
|
||||||
}
|
}
|
||||||
case mautrix.MsgFile:
|
case mautrix.MsgFile:
|
||||||
media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaDocument)
|
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, evt, whatsapp.MediaDocument)
|
||||||
if media == nil {
|
if media == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info.Message.DocumentMessage = &waProto.DocumentMessage{
|
info.Message.DocumentMessage = &waProto.DocumentMessage{
|
||||||
Url: &media.URL,
|
Url: &media.URL,
|
||||||
|
FileName: &evt.Content.Body,
|
||||||
MediaKey: media.MediaKey,
|
MediaKey: media.MediaKey,
|
||||||
Mimetype: &evt.Content.GetInfo().MimeType,
|
Mimetype: &evt.Content.GetInfo().MimeType,
|
||||||
FileEncSha256: media.FileEncSHA256,
|
FileEncSha256: media.FileEncSHA256,
|
||||||
|
|
10
user.go
10
user.go
|
@ -49,6 +49,9 @@ type User struct {
|
||||||
|
|
||||||
Admin bool
|
Admin bool
|
||||||
Whitelisted bool
|
Whitelisted bool
|
||||||
|
RelaybotWhitelisted bool
|
||||||
|
|
||||||
|
IsRelaybot bool
|
||||||
|
|
||||||
ConnectionErrors int
|
ConnectionErrors int
|
||||||
CommunityID string
|
CommunityID string
|
||||||
|
@ -144,10 +147,13 @@ func (bridge *Bridge) NewUser(dbUser *database.User) *User {
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
log: bridge.Log.Sub("User").Sub(string(dbUser.MXID)),
|
log: bridge.Log.Sub("User").Sub(string(dbUser.MXID)),
|
||||||
|
|
||||||
|
IsRelaybot: false,
|
||||||
|
|
||||||
chatListReceived: make(chan struct{}, 1),
|
chatListReceived: make(chan struct{}, 1),
|
||||||
syncPortalsDone: make(chan struct{}, 1),
|
syncPortalsDone: make(chan struct{}, 1),
|
||||||
messages: make(chan PortalMessage, 256),
|
messages: make(chan PortalMessage, 256),
|
||||||
}
|
}
|
||||||
|
user.RelaybotWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelaybotWhitelisted(user.MXID)
|
||||||
user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
|
user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
|
||||||
user.Admin = user.bridge.Config.Bridge.Permissions.IsAdmin(user.MXID)
|
user.Admin = user.bridge.Config.Bridge.Permissions.IsAdmin(user.MXID)
|
||||||
go user.handleMessageLoop()
|
go user.handleMessageLoop()
|
||||||
|
@ -773,3 +779,7 @@ func (user *User) HandleJsonMessage(message string) {
|
||||||
func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
|
func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
|
||||||
user.updateLastConnectionIfNecessary()
|
user.updateLastConnectionIfNecessary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) NeedsRelaybot(portal *Portal) bool {
|
||||||
|
return !user.HasSession() || user.IsInPortal(portal.Key.JID) || portal.IsPrivateChat()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue