2018-08-13 22:24:44 +02:00
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
2019-01-12 14:54:04 +01:00
// Copyright (C) 2019 Tulir Asokan
2018-08-13 00:00:23 +02:00
//
// 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
2018-08-13 22:24:44 +02:00
import (
2019-05-15 22:04:09 +02:00
"fmt"
2018-08-26 16:02:32 +02:00
"strings"
2019-01-11 20:17:31 +01:00
"maunium.net/go/maulogger/v2"
2019-11-10 20:22:11 +01:00
2019-01-11 20:17:31 +01:00
"maunium.net/go/mautrix"
2018-08-26 16:02:32 +02:00
"maunium.net/go/mautrix-appservice"
2019-05-15 22:04:09 +02:00
"maunium.net/go/mautrix/format"
2019-01-11 20:17:31 +01:00
2018-08-26 16:02:32 +02:00
"maunium.net/go/mautrix-whatsapp/types"
2018-08-13 22:24:44 +02:00
)
2018-08-13 00:00:23 +02:00
2018-08-18 21:57:08 +02:00
type MatrixHandler struct {
bridge * Bridge
as * appservice . AppService
log maulogger . Logger
cmd * CommandHandler
}
func NewMatrixHandler ( bridge * Bridge ) * MatrixHandler {
handler := & MatrixHandler {
bridge : bridge ,
2018-08-28 23:40:54 +02:00
as : bridge . AS ,
2018-08-18 21:57:08 +02:00
log : bridge . Log . Sub ( "Matrix" ) ,
cmd : NewCommandHandler ( bridge ) ,
}
2019-01-11 20:17:31 +01:00
bridge . EventProcessor . On ( mautrix . EventMessage , handler . HandleMessage )
2019-12-31 19:17:03 +01:00
bridge . EventProcessor . On ( mautrix . EventSticker , handler . HandleMessage )
2019-05-16 00:59:36 +02:00
bridge . EventProcessor . On ( mautrix . EventRedaction , handler . HandleRedaction )
2019-01-11 20:17:31 +01:00
bridge . EventProcessor . On ( mautrix . StateMember , handler . HandleMembership )
bridge . EventProcessor . On ( mautrix . StateRoomName , handler . HandleRoomMetadata )
bridge . EventProcessor . On ( mautrix . StateRoomAvatar , handler . HandleRoomMetadata )
bridge . EventProcessor . On ( mautrix . StateTopic , handler . HandleRoomMetadata )
2018-08-18 21:57:08 +02:00
return handler
}
2019-01-11 20:17:31 +01:00
func ( mx * MatrixHandler ) HandleBotInvite ( evt * mautrix . Event ) {
2018-08-18 21:57:08 +02:00
intent := mx . as . BotIntent ( )
2018-08-16 14:59:18 +02:00
2018-08-28 23:40:54 +02:00
user := mx . bridge . GetUserByMXID ( evt . Sender )
2018-08-26 16:08:37 +02:00
if user == nil {
return
}
2018-08-16 18:20:07 +02:00
resp , err := intent . JoinRoom ( evt . RoomID , "" , nil )
2018-08-16 14:59:18 +02:00
if err != nil {
2018-08-18 21:57:08 +02:00
mx . log . Debugln ( "Failed to join room" , evt . RoomID , "with invite from" , evt . Sender )
2018-08-16 14:59:18 +02:00
return
}
2018-08-16 18:20:07 +02:00
members , err := intent . JoinedMembers ( resp . RoomID )
2018-08-16 14:59:18 +02:00
if err != nil {
2018-08-18 21:57:08 +02:00
mx . log . Debugln ( "Failed to get members in room" , resp . RoomID , "after accepting invite from" , evt . Sender )
2018-08-16 18:20:07 +02:00
intent . LeaveRoom ( resp . RoomID )
2018-08-16 14:59:18 +02:00
return
}
if len ( members . Joined ) < 2 {
2018-08-18 21:57:08 +02:00
mx . log . Debugln ( "Leaving empty room" , resp . RoomID , "after accepting invite from" , evt . Sender )
2018-08-16 18:20:07 +02:00
intent . LeaveRoom ( resp . RoomID )
2018-08-16 14:59:18 +02:00
return
}
2018-08-16 23:11:28 +02:00
2018-08-26 16:08:37 +02:00
if ! user . Whitelisted {
intent . SendNotice ( resp . RoomID , "You are not whitelisted to use this bridge.\n" +
"If you're the owner of this bridge, see the bridge.permissions section in your config file." )
intent . LeaveRoom ( resp . RoomID )
return
}
2019-11-10 20:22:11 +01:00
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
}
2018-08-16 23:11:28 +02:00
hasPuppets := false
2018-08-16 14:59:18 +02:00
for mxid , _ := range members . Joined {
2018-08-16 18:20:07 +02:00
if mxid == intent . UserID || mxid == evt . Sender {
2018-08-16 14:59:18 +02:00
continue
2018-08-28 23:40:54 +02:00
} else if _ , ok := mx . bridge . ParsePuppetMXID ( types . MatrixUserID ( mxid ) ) ; ok {
2018-08-16 23:11:28 +02:00
hasPuppets = true
2018-08-16 14:59:18 +02:00
continue
}
2018-08-18 21:57:08 +02:00
mx . log . Debugln ( "Leaving multi-user room" , resp . RoomID , "after accepting invite from" , evt . Sender )
2018-08-16 18:20:07 +02:00
intent . SendNotice ( resp . RoomID , "This bridge is user-specific, please don't invite me into rooms with other users." )
intent . LeaveRoom ( resp . RoomID )
2018-08-16 14:59:18 +02:00
return
}
2018-08-16 23:11:28 +02:00
if ! hasPuppets {
2018-08-28 23:40:54 +02:00
user := mx . bridge . GetUserByMXID ( types . MatrixUserID ( evt . Sender ) )
2018-08-18 21:57:08 +02:00
user . SetManagementRoom ( types . MatrixRoomID ( resp . RoomID ) )
2018-12-07 14:31:55 +01:00
intent . SendNotice ( string ( user . ManagementRoom ) , "This room has been registered as your bridge management/status room. Send `help` to get a list of commands." )
2018-08-18 21:57:08 +02:00
mx . log . Debugln ( resp . RoomID , "registered as a management room with" , evt . Sender )
2018-08-16 14:59:18 +02:00
}
}
2019-01-11 20:17:31 +01:00
func ( mx * MatrixHandler ) HandleMembership ( evt * mautrix . Event ) {
2018-08-18 21:57:08 +02:00
if evt . Content . Membership == "invite" && evt . GetStateKey ( ) == mx . as . BotMXID ( ) {
mx . HandleBotInvite ( evt )
2018-08-16 23:11:28 +02:00
}
2019-05-16 19:14:32 +02:00
portal := mx . bridge . GetPortalByMXID ( evt . RoomID )
if portal == nil {
return
}
user := mx . bridge . GetUserByMXID ( types . MatrixUserID ( evt . Sender ) )
2019-08-24 21:39:12 +02:00
if user == nil || ! user . Whitelisted || ! user . IsConnected ( ) {
2019-05-16 19:14:32 +02:00
return
}
if evt . Content . Membership == "leave" {
if evt . GetStateKey ( ) == evt . Sender {
if portal . IsPrivateChat ( ) || evt . Unsigned . PrevContent . Membership == "join" {
portal . HandleMatrixLeave ( user )
}
} else {
portal . HandleMatrixKick ( user , evt )
}
}
2018-08-16 14:59:18 +02:00
}
2019-01-11 20:17:31 +01:00
func ( mx * MatrixHandler ) HandleRoomMetadata ( evt * mautrix . Event ) {
2018-08-28 23:40:54 +02:00
user := mx . bridge . GetUserByMXID ( types . MatrixUserID ( evt . Sender ) )
2019-11-10 20:22:11 +01:00
if user == nil || ! user . Whitelisted || ! user . IsConnected ( ) {
2018-08-26 16:02:32 +02:00
return
}
2018-08-28 23:40:54 +02:00
portal := mx . bridge . GetPortalByMXID ( evt . RoomID )
2018-08-26 16:02:32 +02:00
if portal == nil || portal . IsPrivateChat ( ) {
return
}
var resp <- chan string
var err error
switch evt . Type {
2019-01-11 20:17:31 +01:00
case mautrix . StateRoomName :
2018-08-28 23:40:54 +02:00
resp , err = user . Conn . UpdateGroupSubject ( evt . Content . Name , portal . Key . JID )
2019-01-11 20:17:31 +01:00
case mautrix . StateTopic :
2020-03-31 15:27:42 +02:00
resp , err = user . Conn . UpdateGroupDescription ( portal . Key . JID , evt . Content . Topic )
case mautrix . StateRoomAvatar :
2018-08-26 16:02:32 +02:00
return
}
if err != nil {
mx . log . Errorln ( err )
} else {
out := <- resp
mx . log . Infoln ( out )
}
}
2019-01-11 20:17:31 +01:00
func ( mx * MatrixHandler ) HandleMessage ( evt * mautrix . Event ) {
2018-09-01 22:38:03 +02:00
if _ , isPuppet := mx . bridge . ParsePuppetMXID ( evt . Sender ) ; evt . Sender == mx . bridge . Bot . UserID || isPuppet {
return
}
2019-05-24 01:33:26 +02:00
isCustomPuppet , ok := evt . Content . Raw [ "net.maunium.whatsapp.puppet" ] . ( bool )
if ok && isCustomPuppet && mx . bridge . GetPuppetByCustomMXID ( evt . Sender ) != nil {
return
}
2018-09-01 22:38:03 +02:00
2018-08-18 21:57:08 +02:00
roomID := types . MatrixRoomID ( evt . RoomID )
2018-08-28 23:40:54 +02:00
user := mx . bridge . GetUserByMXID ( types . MatrixUserID ( evt . Sender ) )
2018-08-18 21:57:08 +02:00
2019-11-10 20:22:11 +01:00
if ! user . RelaybotWhitelisted {
2018-08-26 16:08:37 +02:00
return
}
2019-11-10 20:22:11 +01:00
if user . Whitelisted && evt . Content . MsgType == mautrix . MsgText {
2018-08-18 21:57:08 +02:00
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
}
}
2018-08-28 23:40:54 +02:00
portal := mx . bridge . GetPortalByMXID ( roomID )
2019-11-10 20:22:11 +01:00
if portal != nil && ( user . Whitelisted || portal . HasRelaybot ( ) ) {
2018-08-28 23:40:54 +02:00
portal . HandleMatrixMessage ( user , evt )
2018-08-18 21:57:08 +02:00
}
2018-08-13 00:00:23 +02:00
}
2019-05-16 00:59:36 +02:00
func ( mx * MatrixHandler ) HandleRedaction ( evt * mautrix . Event ) {
if _ , isPuppet := mx . bridge . ParsePuppetMXID ( evt . Sender ) ; evt . Sender == mx . bridge . Bot . UserID || isPuppet {
return
}
roomID := types . MatrixRoomID ( evt . RoomID )
user := mx . bridge . GetUserByMXID ( types . MatrixUserID ( evt . Sender ) )
if ! user . Whitelisted {
return
}
2019-08-24 21:39:12 +02:00
if ! user . HasSession ( ) {
2019-05-16 00:59:36 +02:00
return
2019-08-24 21:39:12 +02:00
} else if ! user . IsConnected ( ) {
2019-11-10 20:22:11 +01:00
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. " +
2019-05-16 00:59:36 +02:00
"Use `%[2]s reconnect` to reconnect." , user . MXID , mx . bridge . Config . Bridge . CommandPrefix ) )
msg . MsgType = mautrix . MsgNotice
_ , _ = mx . bridge . Bot . SendMessageEvent ( roomID , mautrix . EventMessage , msg )
return
}
portal := mx . bridge . GetPortalByMXID ( roomID )
if portal != nil {
portal . HandleMatrixRedaction ( user , evt )
}
}