2018-08-13 22:24:44 +02:00
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
2022-02-23 13:30:21 +01:00
// Copyright (C) 2022 Tulir Asokan
2018-08-12 21:26:05 +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 00:00:23 +02:00
import (
2021-10-31 19:42:53 +01:00
_ "embed"
2021-06-01 12:32:14 +02:00
"errors"
2018-08-13 00:00:23 +02:00
"fmt"
2022-03-12 19:05:57 +01:00
"net/http"
2018-08-13 00:00:23 +02:00
"os"
2018-08-13 22:24:44 +02:00
"os/signal"
2021-10-22 19:14:34 +02:00
"strconv"
2020-06-03 19:32:53 +02:00
"strings"
2018-08-28 23:40:54 +02:00
"sync"
2018-08-13 22:24:44 +02:00
"syscall"
2019-11-10 20:22:11 +01:00
"time"
2018-08-26 21:53:13 +02:00
2021-10-22 19:14:34 +02:00
"google.golang.org/protobuf/proto"
2022-03-12 19:05:57 +01:00
"go.mau.fi/whatsmeow"
2021-10-22 19:14:34 +02:00
waProto "go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
2021-02-17 00:21:30 +01:00
2018-08-26 21:53:13 +02:00
flag "maunium.net/go/mauflag"
2019-01-11 20:17:31 +01:00
log "maunium.net/go/maulogger/v2"
2019-05-28 20:29:43 +02:00
2019-11-10 20:22:11 +01:00
"maunium.net/go/mautrix"
2020-05-09 13:31:06 +02:00
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/event"
2020-05-08 21:32:22 +02:00
"maunium.net/go/mautrix/id"
2019-01-11 20:17:31 +01:00
2018-08-26 21:53:13 +02:00
"maunium.net/go/mautrix-whatsapp/config"
2018-08-13 22:24:44 +02:00
"maunium.net/go/mautrix-whatsapp/database"
2019-11-10 20:22:11 +01:00
"maunium.net/go/mautrix-whatsapp/database/upgrades"
2018-08-13 00:00:23 +02:00
)
2021-10-28 12:57:15 +02:00
// The name and repo URL of the bridge.
2020-06-03 19:32:53 +02:00
var (
Name = "mautrix-whatsapp"
2021-08-06 16:46:57 +02:00
URL = "https://github.com/mautrix/whatsapp"
2021-10-28 12:57:15 +02:00
)
// Information to find out exactly which commit the bridge was built from.
// These are filled at build time with the -X linker flag.
var (
2020-06-03 19:59:44 +02:00
Tag = "unknown"
Commit = "unknown"
2020-06-03 19:32:53 +02:00
BuildTime = "unknown"
)
2021-10-28 12:57:15 +02:00
var (
// Version is the version number of the bridge. Changed manually when making a release.
2022-05-16 19:31:31 +02:00
Version = "0.4.0"
2021-10-28 12:57:15 +02:00
// WAVersion is the version number exposed to WhatsApp. Filled in init()
WAVersion = ""
// VersionString is the bridge version, plus commit information. Filled in init() using the build-time values.
VersionString = ""
)
2021-10-31 12:04:44 +01:00
//go:embed example-config.yaml
var ExampleConfig string
2020-06-03 19:32:53 +02:00
func init ( ) {
if len ( Tag ) > 0 && Tag [ 0 ] == 'v' {
Tag = Tag [ 1 : ]
}
2021-04-25 14:46:06 +02:00
if Tag != Version {
suffix := ""
if ! strings . HasSuffix ( Version , "+dev" ) {
suffix = "+dev"
}
if len ( Commit ) > 8 {
Version = fmt . Sprintf ( "%s%s.%s" , Version , suffix , Commit [ : 8 ] )
} else {
Version = fmt . Sprintf ( "%s%s.unknown" , Version , suffix )
}
2020-06-03 19:44:52 +02:00
}
2021-03-26 10:18:10 +01:00
mautrix . DefaultUserAgent = fmt . Sprintf ( "mautrix-whatsapp/%s %s" , Version , mautrix . DefaultUserAgent )
2020-06-03 19:59:44 +02:00
WAVersion = strings . FieldsFunc ( Version , func ( r rune ) bool { return r == '-' || r == '+' } ) [ 0 ]
2021-04-25 14:46:06 +02:00
VersionString = fmt . Sprintf ( "%s %s (%s)" , Name , Version , BuildTime )
2021-10-31 12:04:44 +01:00
config . ExampleConfig = ExampleConfig
2020-06-03 19:32:53 +02:00
}
2018-08-13 22:24:44 +02:00
var configPath = flag . MakeFull ( "c" , "config" , "The path to your config file." , "config.yaml" ) . String ( )
2021-11-07 21:31:22 +01:00
var dontSaveConfig = flag . MakeFull ( "n" , "no-update" , "Don't save updated config to disk." , "false" ) . Bool ( )
2018-08-13 22:24:44 +02:00
var registrationPath = flag . MakeFull ( "r" , "registration" , "The path where to save the appservice registration." , "registration.yaml" ) . String ( )
var generateRegistration = flag . MakeFull ( "g" , "generate-registration" , "Generate registration and quit." , "false" ) . Bool ( )
2020-06-03 19:32:53 +02:00
var version = flag . MakeFull ( "v" , "version" , "View bridge version and quit." , "false" ) . Bool ( )
2022-03-16 00:15:13 +01:00
var ignoreUnsupportedDatabase = flag . Make ( ) . LongKey ( "ignore-unsupported-database" ) . Usage ( "Run even if the database schema is too new" ) . Default ( "false" ) . Bool ( )
var ignoreForeignTables = flag . Make ( ) . LongKey ( "ignore-foreign-tables" ) . Usage ( "Run even if the database contains tables from other programs (like Synapse)" ) . Default ( "false" ) . Bool ( )
2019-08-25 18:26:04 +02:00
var migrateFrom = flag . Make ( ) . LongKey ( "migrate-db" ) . Usage ( "Source database type and URI to migrate from." ) . Bool ( )
2018-08-13 22:24:44 +02:00
var wantHelp , _ = flag . MakeHelpFlag ( )
func ( bridge * Bridge ) GenerateRegistration ( ) {
2021-11-07 21:31:22 +01:00
if * dontSaveConfig {
// We need to save the generated as_token and hs_token in the config
_ , _ = fmt . Fprintln ( os . Stderr , "--no-update is not compatible with --generate-registration" )
os . Exit ( 5 )
}
2018-08-13 22:24:44 +02:00
reg , err := bridge . Config . NewRegistration ( )
if err != nil {
2020-06-17 16:50:06 +02:00
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to generate registration:" , err )
2018-08-13 22:24:44 +02:00
os . Exit ( 20 )
}
err = reg . Save ( * registrationPath )
if err != nil {
2020-06-17 16:50:06 +02:00
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to save registration:" , err )
2018-08-13 22:24:44 +02:00
os . Exit ( 21 )
}
2021-11-07 21:31:22 +01:00
err = config . Mutate ( * configPath , func ( helper * config . UpgradeHelper ) {
helper . Set ( config . Str , bridge . Config . AppService . ASToken , "appservice" , "as_token" )
helper . Set ( config . Str , bridge . Config . AppService . HSToken , "appservice" , "hs_token" )
} )
2018-08-13 22:24:44 +02:00
if err != nil {
2020-06-17 16:50:06 +02:00
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to save config:" , err )
2018-08-13 22:24:44 +02:00
os . Exit ( 22 )
}
2019-04-06 21:41:15 +02:00
fmt . Println ( "Registration generated. Add the path to the registration to your Synapse config, restart it, then start the bridge." )
2018-08-13 22:24:44 +02:00
os . Exit ( 0 )
}
2019-08-25 18:26:04 +02:00
func ( bridge * Bridge ) MigrateDatabase ( ) {
2022-02-23 13:30:21 +01:00
oldDB , err := database . New ( config . DatabaseConfig { Type : flag . Arg ( 0 ) , URI : flag . Arg ( 1 ) } , log . DefaultLogger )
2019-08-25 18:26:04 +02:00
if err != nil {
fmt . Println ( "Failed to open old database:" , err )
os . Exit ( 30 )
}
err = oldDB . Init ( )
if err != nil {
fmt . Println ( "Failed to upgrade old database:" , err )
os . Exit ( 31 )
}
2022-02-23 13:30:21 +01:00
newDB , err := database . New ( bridge . Config . AppService . Database , log . DefaultLogger )
2019-08-25 18:26:04 +02:00
if err != nil {
2020-07-30 20:41:04 +02:00
fmt . Println ( "Failed to open new database:" , err )
2019-08-25 18:26:04 +02:00
os . Exit ( 32 )
}
err = newDB . Init ( )
if err != nil {
fmt . Println ( "Failed to upgrade new database:" , err )
os . Exit ( 33 )
}
database . Migrate ( oldDB , newDB )
}
2018-08-13 22:24:44 +02:00
type Bridge struct {
2018-08-28 23:40:54 +02:00
AS * appservice . AppService
2018-08-16 23:11:28 +02:00
EventProcessor * appservice . EventProcessor
2018-08-18 21:57:08 +02:00
MatrixHandler * MatrixHandler
2018-08-16 23:11:28 +02:00
Config * config . Config
DB * database . Database
Log log . Logger
2019-08-25 15:29:35 +02:00
StateStore * database . SQLStateStore
2020-02-09 19:32:14 +01:00
Provisioning * ProvisioningAPI
2018-08-28 23:40:54 +02:00
Bot * appservice . IntentAPI
Formatter * Formatter
2020-05-09 01:03:59 +02:00
Crypto Crypto
2020-09-24 14:25:36 +02:00
Metrics * MetricsHandler
2021-10-22 19:14:34 +02:00
WAContainer * sqlstore . Container
2018-08-28 23:40:54 +02:00
2020-05-08 21:32:22 +02:00
usersByMXID map [ id . UserID ] * User
2021-10-22 19:14:34 +02:00
usersByUsername map [ string ] * User
2018-08-28 23:40:54 +02:00
usersLock sync . Mutex
2021-12-28 21:14:09 +01:00
spaceRooms map [ id . RoomID ] * User
2021-12-29 13:19:16 +01:00
spaceRoomsLock sync . Mutex
2021-12-28 21:14:09 +01:00
managementRooms map [ id . RoomID ] * User
2021-12-29 13:19:16 +01:00
managementRoomsLock sync . Mutex
2020-05-08 21:32:22 +02:00
portalsByMXID map [ id . RoomID ] * Portal
2018-08-28 23:40:54 +02:00
portalsByJID map [ database . PortalKey ] * Portal
portalsLock sync . Mutex
2021-10-22 19:14:34 +02:00
puppets map [ types . JID ] * Puppet
2020-05-08 21:32:22 +02:00
puppetsByCustomMXID map [ id . UserID ] * Puppet
2018-08-28 23:40:54 +02:00
puppetsLock sync . Mutex
2018-08-13 22:24:44 +02:00
}
2020-05-09 01:03:59 +02:00
type Crypto interface {
HandleMemberEvent ( * event . Event )
Decrypt ( * event . Event ) ( * event . Event , error )
Encrypt ( id . RoomID , event . Type , event . Content ) ( * event . EncryptedEventContent , error )
2020-09-24 14:25:36 +02:00
WaitForSession ( id . RoomID , id . SenderKey , id . SessionID , time . Duration ) bool
2021-12-03 17:24:24 +01:00
RequestSession ( id . RoomID , id . SenderKey , id . SessionID , id . UserID , id . DeviceID )
2020-10-05 21:32:15 +02:00
ResetSession ( id . RoomID )
2020-05-09 19:07:21 +02:00
Init ( ) error
2020-05-09 01:03:59 +02:00
Start ( )
Stop ( )
}
2019-08-24 23:34:58 +02:00
func ( bridge * Bridge ) ensureConnection ( ) {
for {
2022-05-10 10:33:13 +02:00
versions , err := bridge . Bot . Versions ( )
if err != nil {
bridge . Log . Errorfln ( "Failed to connect to homeserver: %v. Retrying in 10 seconds..." , err )
time . Sleep ( 10 * time . Second )
continue
}
if ! versions . ContainsGreaterOrEqual ( mautrix . SpecV11 ) {
bridge . Log . Warnfln ( "Server isn't advertising modern spec versions" )
}
2020-05-08 21:32:22 +02:00
resp , err := bridge . Bot . Whoami ( )
2019-08-24 23:34:58 +02:00
if err != nil {
2021-06-01 12:32:14 +02:00
if errors . Is ( err , mautrix . MUnknownToken ) {
2021-11-29 18:47:04 +01:00
bridge . Log . Fatalln ( "The as_token was not accepted. Is the registration file installed in your homeserver correctly?" )
os . Exit ( 16 )
} else if errors . Is ( err , mautrix . MExclusive ) {
bridge . Log . Fatalln ( "The as_token was accepted, but the /register request was not. Are the homeserver domain and username template in the config correct, and do they match the values in the registration?" )
2019-08-24 23:34:58 +02:00
os . Exit ( 16 )
}
bridge . Log . Errorfln ( "Failed to connect to homeserver: %v. Retrying in 10 seconds..." , err )
time . Sleep ( 10 * time . Second )
} else if resp . UserID != bridge . Bot . UserID {
bridge . Log . Fatalln ( "Unexpected user ID in whoami call: got %s, expected %s" , resp . UserID , bridge . Bot . UserID )
os . Exit ( 17 )
} else {
break
}
}
}
2018-08-13 22:24:44 +02:00
func ( bridge * Bridge ) Init ( ) {
var err error
2018-08-16 18:20:07 +02:00
2018-08-28 23:40:54 +02:00
bridge . AS , err = bridge . Config . MakeAppService ( )
2018-08-13 22:24:44 +02:00
if err != nil {
2019-08-24 21:45:16 +02:00
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to initialize AppService:" , err )
2018-08-29 22:48:15 +02:00
os . Exit ( 11 )
2018-08-13 22:24:44 +02:00
}
2019-08-24 21:45:16 +02:00
_ , _ = bridge . AS . Init ( )
2018-08-28 23:40:54 +02:00
bridge . Log = log . Create ( )
bridge . Config . Logging . Configure ( bridge . Log )
2018-08-16 18:20:07 +02:00
log . DefaultLogger = bridge . Log . ( * log . BasicLogger )
2019-08-24 21:45:16 +02:00
if len ( bridge . Config . Logging . FileNameFormat ) > 0 {
err = log . OpenFile ( )
if err != nil {
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to open log file:" , err )
os . Exit ( 12 )
}
2018-08-28 23:40:54 +02:00
}
bridge . AS . Log = log . Sub ( "Matrix" )
2021-11-02 11:27:38 +01:00
bridge . Bot = bridge . AS . BotIntent ( )
2021-04-25 14:46:06 +02:00
bridge . Log . Infoln ( "Initializing" , VersionString )
2018-08-16 18:20:07 +02:00
2020-11-02 15:16:36 +01:00
bridge . Log . Debugln ( "Initializing database connection" )
2022-02-23 13:30:21 +01:00
bridge . DB , err = database . New ( bridge . Config . AppService . Database , bridge . Log )
2020-11-02 15:16:36 +01:00
if err != nil {
bridge . Log . Fatalln ( "Failed to initialize database connection:" , err )
2018-08-29 22:48:15 +02:00
os . Exit ( 14 )
2018-08-13 22:24:44 +02:00
}
2019-08-25 15:29:35 +02:00
bridge . Log . Debugln ( "Initializing state store" )
bridge . StateStore = database . NewSQLStateStore ( bridge . DB )
bridge . AS . StateStore = bridge . StateStore
2022-05-16 12:46:18 +02:00
Segment . log = bridge . Log . Sub ( "Segment" )
Segment . key = bridge . Config . SegmentKey
if Segment . IsEnabled ( ) {
Segment . log . Infoln ( "Segment metrics are enabled" )
}
2021-10-22 19:14:34 +02:00
bridge . WAContainer = sqlstore . NewWithDB ( bridge . DB . DB , bridge . Config . AppService . Database . Type , nil )
2022-04-29 18:38:44 +02:00
bridge . WAContainer . DatabaseErrorHandler = bridge . DB . HandleSignalStoreError
2021-10-22 19:14:34 +02:00
2020-02-09 19:32:14 +01:00
ss := bridge . Config . AppService . Provisioning . SharedSecret
if len ( ss ) > 0 && ss != "disable" {
bridge . Provisioning = & ProvisioningAPI { bridge : bridge }
}
2018-08-18 21:57:08 +02:00
bridge . Log . Debugln ( "Initializing Matrix event processor" )
2018-08-28 23:40:54 +02:00
bridge . EventProcessor = appservice . NewEventProcessor ( bridge . AS )
2018-08-18 21:57:08 +02:00
bridge . Log . Debugln ( "Initializing Matrix event handler" )
bridge . MatrixHandler = NewMatrixHandler ( bridge )
2018-08-28 23:40:54 +02:00
bridge . Formatter = NewFormatter ( bridge )
2020-05-09 19:07:21 +02:00
bridge . Crypto = NewCryptoHelper ( bridge )
2020-06-17 16:50:06 +02:00
bridge . Metrics = NewMetricsHandler ( bridge . Config . Metrics . Listen , bridge . Log . Sub ( "Metrics" ) , bridge . DB )
2021-10-22 19:14:34 +02:00
store . BaseClientPayload . UserAgent . OsVersion = proto . String ( WAVersion )
store . BaseClientPayload . UserAgent . OsBuildNumber = proto . String ( WAVersion )
store . CompanionProps . Os = proto . String ( bridge . Config . WhatsApp . OSName )
2021-10-30 22:12:16 +02:00
store . CompanionProps . RequireFullSync = proto . Bool ( bridge . Config . Bridge . HistorySync . RequestFullSync )
2021-10-22 19:14:34 +02:00
versionParts := strings . Split ( WAVersion , "." )
if len ( versionParts ) > 2 {
primary , _ := strconv . Atoi ( versionParts [ 0 ] )
secondary , _ := strconv . Atoi ( versionParts [ 1 ] )
tertiary , _ := strconv . Atoi ( versionParts [ 2 ] )
store . CompanionProps . Version . Primary = proto . Uint32 ( uint32 ( primary ) )
store . CompanionProps . Version . Secondary = proto . Uint32 ( uint32 ( secondary ) )
store . CompanionProps . Version . Tertiary = proto . Uint32 ( uint32 ( tertiary ) )
}
platformID , ok := waProto . CompanionProps_CompanionPropsPlatformType_value [ strings . ToUpper ( bridge . Config . WhatsApp . BrowserName ) ]
if ok {
store . CompanionProps . PlatformType = waProto . CompanionProps_CompanionPropsPlatformType ( platformID ) . Enum ( )
}
2018-08-13 22:24:44 +02:00
}
func ( bridge * Bridge ) Start ( ) {
2020-11-02 15:16:36 +01:00
bridge . Log . Debugln ( "Running database upgrades" )
2019-08-25 15:29:35 +02:00
err := bridge . DB . Init ( )
2022-03-16 00:15:13 +01:00
if err != nil && ( ! errors . Is ( err , upgrades . ErrUnsupportedDatabaseVersion ) || ! * ignoreUnsupportedDatabase ) {
2019-05-16 19:14:32 +02:00
bridge . Log . Fatalln ( "Failed to initialize database:" , err )
2022-03-16 00:15:13 +01:00
if errors . Is ( err , upgrades . ErrForeignTables ) {
bridge . Log . Infoln ( "You can use --ignore-foreign-tables to ignore this error" )
} else if errors . Is ( err , upgrades . ErrNotOwned ) {
bridge . Log . Infoln ( "Sharing the same database with different programs is not supported" )
} else if errors . Is ( err , upgrades . ErrUnsupportedDatabaseVersion ) {
bridge . Log . Infoln ( "Downgrading the bridge is not supported" )
}
2018-08-29 22:48:15 +02:00
os . Exit ( 15 )
2018-08-18 21:57:08 +02:00
}
2020-07-23 18:18:24 +02:00
bridge . Log . Debugln ( "Checking connection to homeserver" )
bridge . ensureConnection ( )
2020-05-09 19:07:21 +02:00
if bridge . Crypto != nil {
2021-08-04 15:14:26 +02:00
err = bridge . Crypto . Init ( )
2020-05-09 19:07:21 +02:00
if err != nil {
bridge . Log . Fatalln ( "Error initializing end-to-bridge encryption:" , err )
os . Exit ( 19 )
}
}
2020-02-09 19:32:14 +01:00
if bridge . Provisioning != nil {
bridge . Log . Debugln ( "Initializing provisioning API" )
bridge . Provisioning . Init ( )
}
2018-08-16 23:11:28 +02:00
bridge . Log . Debugln ( "Starting application service HTTP server" )
2018-08-28 23:40:54 +02:00
go bridge . AS . Start ( )
2018-08-16 23:11:28 +02:00
bridge . Log . Debugln ( "Starting event processor" )
go bridge . EventProcessor . Start ( )
2022-03-12 19:05:57 +01:00
go bridge . CheckWhatsAppUpdate ( )
2018-08-16 18:20:07 +02:00
go bridge . UpdateBotProfile ( )
2020-05-21 20:56:46 +02:00
if bridge . Crypto != nil {
go bridge . Crypto . Start ( )
}
2018-08-18 21:57:08 +02:00
go bridge . StartUsers ( )
2020-06-17 16:50:06 +02:00
if bridge . Config . Metrics . Enabled {
go bridge . Metrics . Start ( )
}
2020-06-15 19:28:04 +02:00
if bridge . Config . Bridge . ResendBridgeInfo {
go bridge . ResendBridgeInfo ( )
}
2022-01-25 13:26:24 +01:00
go bridge . Loop ( )
2021-08-25 19:46:52 +02:00
bridge . AS . Ready = true
2020-06-15 19:28:04 +02:00
}
2022-03-12 19:05:57 +01:00
func ( bridge * Bridge ) CheckWhatsAppUpdate ( ) {
bridge . Log . Debugfln ( "Checking for WhatsApp web update" )
resp , err := whatsmeow . CheckUpdate ( http . DefaultClient )
if err != nil {
bridge . Log . Warnfln ( "Failed to check for WhatsApp web update: %v" , err )
return
}
if store . GetWAVersion ( ) == resp . ParsedVersion {
bridge . Log . Debugfln ( "Bridge is using latest WhatsApp web protocol" )
} else if store . GetWAVersion ( ) . LessThan ( resp . ParsedVersion ) {
2022-03-23 13:46:30 +01:00
if resp . IsBelowHard || resp . IsBroken {
bridge . Log . Warnfln ( "Bridge is using outdated WhatsApp web protocol and probably doesn't work anymore (%s, latest is %s)" , store . GetWAVersion ( ) , resp . ParsedVersion )
} else if resp . IsBelowSoft {
bridge . Log . Infofln ( "Bridge is using outdated WhatsApp web protocol (%s, latest is %s)" , store . GetWAVersion ( ) , resp . ParsedVersion )
2022-03-12 19:05:57 +01:00
} else {
bridge . Log . Debugfln ( "Bridge is using outdated WhatsApp web protocol (%s, latest is %s)" , store . GetWAVersion ( ) , resp . ParsedVersion )
}
} else {
bridge . Log . Debugfln ( "Bridge is using newer than latest WhatsApp web protocol" )
}
}
2022-01-25 13:26:24 +01:00
func ( bridge * Bridge ) Loop ( ) {
for {
bridge . SleepAndDeleteUpcoming ( )
time . Sleep ( 1 * time . Hour )
bridge . WarnUsersAboutDisconnection ( )
}
}
func ( bridge * Bridge ) WarnUsersAboutDisconnection ( ) {
bridge . usersLock . Lock ( )
for _ , user := range bridge . usersByUsername {
2022-02-18 11:12:15 +01:00
if user . IsConnected ( ) && ! user . PhoneRecentlySeen ( true ) {
2022-01-25 13:26:24 +01:00
go user . sendPhoneOfflineWarning ( )
}
}
bridge . usersLock . Unlock ( )
}
2020-06-15 19:28:04 +02:00
func ( bridge * Bridge ) ResendBridgeInfo ( ) {
2021-11-07 21:31:22 +01:00
if * dontSaveConfig {
bridge . Log . Warnln ( "Not setting resend_bridge_info to false in config due to --no-update flag" )
} else {
err := config . Mutate ( * configPath , func ( helper * config . UpgradeHelper ) {
helper . Set ( config . Bool , "false" , "bridge" , "resend_bridge_info" )
} )
if err != nil {
bridge . Log . Errorln ( "Failed to save config after setting resend_bridge_info to false:" , err )
}
2020-06-15 19:28:04 +02:00
}
bridge . Log . Infoln ( "Re-sending bridge info state event to all portals" )
for _ , portal := range bridge . GetAllPortals ( ) {
portal . UpdateBridgeInfo ( )
}
bridge . Log . Infoln ( "Finished re-sending bridge info state events" )
2018-08-16 18:20:07 +02:00
}
func ( bridge * Bridge ) UpdateBotProfile ( ) {
2018-08-18 21:57:08 +02:00
bridge . Log . Debugln ( "Updating bot profile" )
2021-12-29 20:40:08 +01:00
botConfig := & bridge . Config . AppService . Bot
2018-08-16 18:20:07 +02:00
var err error
2020-05-08 21:32:22 +02:00
var mxc id . ContentURI
2018-08-16 18:20:07 +02:00
if botConfig . Avatar == "remove" {
2020-05-08 21:32:22 +02:00
err = bridge . Bot . SetAvatarURL ( mxc )
2018-08-16 18:20:07 +02:00
} else if len ( botConfig . Avatar ) > 0 {
2020-05-08 21:32:22 +02:00
mxc , err = id . ParseContentURI ( botConfig . Avatar )
if err == nil {
err = bridge . Bot . SetAvatarURL ( mxc )
}
2021-12-29 20:40:08 +01:00
botConfig . ParsedAvatar = mxc
2018-08-16 18:20:07 +02:00
}
if err != nil {
bridge . Log . Warnln ( "Failed to update bot avatar:" , err )
}
if botConfig . Displayname == "remove" {
2018-08-30 00:10:26 +02:00
err = bridge . Bot . SetDisplayName ( "" )
2021-12-30 11:33:00 +01:00
} else if len ( botConfig . Displayname ) > 0 {
2018-08-30 00:10:26 +02:00
err = bridge . Bot . SetDisplayName ( botConfig . Displayname )
2018-08-16 18:20:07 +02:00
}
if err != nil {
bridge . Log . Warnln ( "Failed to update bot displayname:" , err )
}
2018-08-13 22:24:44 +02:00
}
2018-08-18 21:57:08 +02:00
func ( bridge * Bridge ) StartUsers ( ) {
2019-03-06 16:33:42 +01:00
bridge . Log . Debugln ( "Starting users" )
2021-08-04 15:14:26 +02:00
foundAnySessions := false
2018-08-18 21:57:08 +02:00
for _ , user := range bridge . GetAllUsers ( ) {
2021-10-22 19:14:34 +02:00
if ! user . JID . IsEmpty ( ) {
2021-08-04 15:14:26 +02:00
foundAnySessions = true
}
2021-10-27 14:54:34 +02:00
go user . Connect ( )
2018-08-18 21:57:08 +02:00
}
2021-08-04 15:14:26 +02:00
if ! foundAnySessions {
bridge . sendGlobalBridgeState ( BridgeState { StateEvent : StateUnconfigured } . fill ( nil ) )
}
2019-05-24 01:33:26 +02:00
bridge . Log . Debugln ( "Starting custom puppets" )
2019-05-31 22:07:33 +02:00
for _ , loopuppet := range bridge . GetAllPuppetsWithCustomMXID ( ) {
go func ( puppet * Puppet ) {
2019-05-24 01:33:26 +02:00
puppet . log . Debugln ( "Starting custom puppet" , puppet . CustomMXID )
2021-02-11 19:47:10 +01:00
err := puppet . StartCustomMXID ( true )
2019-05-24 01:33:26 +02:00
if err != nil {
puppet . log . Errorln ( "Failed to start custom puppet:" , err )
}
2019-05-31 22:07:33 +02:00
} ( loopuppet )
2019-05-24 01:33:26 +02:00
}
2018-08-18 21:57:08 +02:00
}
2018-08-13 22:24:44 +02:00
func ( bridge * Bridge ) Stop ( ) {
2020-05-21 20:56:46 +02:00
if bridge . Crypto != nil {
bridge . Crypto . Stop ( )
}
2018-08-28 23:40:54 +02:00
bridge . AS . Stop ( )
2020-06-17 16:50:06 +02:00
bridge . Metrics . Stop ( )
2018-08-16 23:11:28 +02:00
bridge . EventProcessor . Stop ( )
2021-10-22 19:14:34 +02:00
for _ , user := range bridge . usersByUsername {
if user . Client == nil {
2019-05-23 21:57:19 +02:00
continue
}
2019-05-23 18:16:29 +02:00
bridge . Log . Debugln ( "Disconnecting" , user . MXID )
2021-10-22 19:14:34 +02:00
user . Client . Disconnect ( )
2021-10-26 16:01:10 +02:00
close ( user . historySyncs )
2019-05-23 18:16:29 +02:00
}
2018-08-13 22:24:44 +02:00
}
func ( bridge * Bridge ) Main ( ) {
2021-11-07 21:31:22 +01:00
configData , upgraded , err := config . Upgrade ( * configPath , ! * dontSaveConfig )
if err != nil {
_ , _ = fmt . Fprintln ( os . Stderr , "Error updating config:" , err )
if configData == nil {
os . Exit ( 10 )
}
}
bridge . Config , err = config . Load ( configData , upgraded )
if err != nil {
_ , _ = fmt . Fprintln ( os . Stderr , "Failed to parse config:" , err )
os . Exit ( 10 )
}
2018-08-13 22:24:44 +02:00
if * generateRegistration {
bridge . GenerateRegistration ( )
return
2019-08-25 18:26:04 +02:00
} else if * migrateFrom {
bridge . MigrateDatabase ( )
return
2018-08-13 22:24:44 +02:00
}
bridge . Init ( )
bridge . Log . Infoln ( "Bridge initialization complete, starting..." )
bridge . Start ( )
bridge . Log . Infoln ( "Bridge started!" )
c := make ( chan os . Signal )
signal . Notify ( c , os . Interrupt , syscall . SIGTERM )
<- c
bridge . Log . Infoln ( "Interrupt received, stopping..." )
bridge . Stop ( )
bridge . Log . Infoln ( "Bridge stopped." )
os . Exit ( 0 )
}
2018-08-12 21:26:05 +02:00
func main ( ) {
2018-08-16 14:59:18 +02:00
flag . SetHelpTitles (
"mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge." ,
2019-08-25 18:26:04 +02:00
"mautrix-whatsapp [-h] [-c <path>] [-r <path>] [-g] [--migrate-db <source type> <source uri>]" )
2018-08-13 22:24:44 +02:00
err := flag . Parse ( )
if err != nil {
2020-06-17 16:50:06 +02:00
_ , _ = fmt . Fprintln ( os . Stderr , err )
2018-08-13 22:24:44 +02:00
flag . PrintHelp ( )
os . Exit ( 1 )
} else if * wantHelp {
flag . PrintHelp ( )
os . Exit ( 0 )
2020-06-03 19:47:13 +02:00
} else if * version {
2021-04-25 14:46:06 +02:00
fmt . Println ( VersionString )
2020-06-03 19:47:13 +02:00
return
2018-08-13 22:24:44 +02:00
}
2022-03-16 00:15:13 +01:00
upgrades . IgnoreForeignTables = * ignoreForeignTables
2018-08-13 22:24:44 +02:00
2021-11-07 21:31:22 +01:00
( & Bridge {
usersByMXID : make ( map [ id . UserID ] * User ) ,
usersByUsername : make ( map [ string ] * User ) ,
2021-12-29 13:19:16 +01:00
spaceRooms : make ( map [ id . RoomID ] * User ) ,
2021-11-07 21:31:22 +01:00
managementRooms : make ( map [ id . RoomID ] * User ) ,
portalsByMXID : make ( map [ id . RoomID ] * Portal ) ,
portalsByJID : make ( map [ database . PortalKey ] * Portal ) ,
puppets : make ( map [ types . JID ] * Puppet ) ,
puppetsByCustomMXID : make ( map [ id . UserID ] * Puppet ) ,
} ) . Main ( )
2018-08-13 22:24:44 +02:00
}