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"
2022-03-12 19:05:57 +01:00
"net/http"
2018-08-13 00:00:23 +02:00
"os"
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"
2019-11-10 20:22:11 +01:00
"time"
2018-08-26 21:53:13 +02:00
2022-05-23 23:08:28 +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
2022-06-23 19:25:09 +02:00
"maunium.net/go/mautrix"
2022-05-22 00:06:30 +02:00
"maunium.net/go/mautrix/bridge"
2022-05-22 15:15:54 +02:00
"maunium.net/go/mautrix/bridge/commands"
2022-08-15 15:36:28 +02:00
"maunium.net/go/mautrix/bridge/status"
2022-05-22 15:15:54 +02:00
"maunium.net/go/mautrix/event"
2020-05-08 21:32:22 +02:00
"maunium.net/go/mautrix/id"
2022-05-21 11:06:07 +02:00
"maunium.net/go/mautrix/util/configupgrade"
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"
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-31 12:04:44 +01:00
//go:embed example-config.yaml
var ExampleConfig string
2022-05-22 00:06:30 +02:00
type WABridge struct {
bridge . Bridge
2022-05-22 15:15:54 +02:00
Config * config . Config
DB * database . Database
Provisioning * ProvisioningAPI
Formatter * Formatter
Metrics * MetricsHandler
WAContainer * sqlstore . Container
WAVersion string
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
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) Init ( ) {
2022-05-22 15:15:54 +02:00
br . CommandProcessor = commands . NewProcessor ( & br . Bridge )
br . RegisterCommands ( )
// TODO this is a weird place for this
br . EventProcessor . On ( event . EphemeralEventPresence , br . HandlePresence )
2022-12-23 14:17:57 +01:00
br . EventProcessor . On ( TypeMSC3381PollStart , br . MatrixHandler . HandleMessage )
br . EventProcessor . On ( TypeMSC3381PollResponse , br . MatrixHandler . HandleMessage )
br . EventProcessor . On ( TypeMSC3381V2PollResponse , br . MatrixHandler . HandleMessage )
2022-05-22 15:15:54 +02:00
2022-05-22 00:06:30 +02:00
Segment . log = br . Log . Sub ( "Segment" )
Segment . key = br . Config . SegmentKey
2023-01-31 20:21:37 +01:00
Segment . userID = br . Config . SegmentUserID
2022-05-16 12:46:18 +02:00
if Segment . IsEnabled ( ) {
Segment . log . Infoln ( "Segment metrics are enabled" )
2023-01-31 20:11:01 +01:00
if Segment . userID != "" {
Segment . log . Infoln ( "Overriding Segment user_id with %v" , Segment . userID )
}
2022-05-16 12:46:18 +02:00
}
2022-08-14 18:26:42 +02:00
br . DB = database . New ( br . Bridge . DB , br . Log . Sub ( "Database" ) )
br . WAContainer = sqlstore . NewWithDB ( br . DB . RawDB , br . DB . Dialect . String ( ) , & waLogger { br . Log . Sub ( "Database" ) . Sub ( "WhatsApp" ) } )
2022-05-22 00:06:30 +02:00
br . WAContainer . DatabaseErrorHandler = br . DB . HandleSignalStoreError
2021-10-22 19:14:34 +02:00
2022-05-22 00:06:30 +02:00
ss := br . Config . Bridge . Provisioning . SharedSecret
2020-02-09 19:32:14 +01:00
if len ( ss ) > 0 && ss != "disable" {
2022-05-22 00:06:30 +02:00
br . Provisioning = & ProvisioningAPI { bridge : br }
2020-02-09 19:32:14 +01:00
}
2022-05-22 00:06:30 +02:00
br . Formatter = NewFormatter ( br )
br . Metrics = NewMetricsHandler ( br . Config . Metrics . Listen , br . Log . Sub ( "Metrics" ) , br . DB )
2022-05-22 16:32:22 +02:00
br . MatrixHandler . TrackEventDuration = br . Metrics . TrackMatrixEvent
2021-10-22 19:14:34 +02:00
2022-05-22 00:06:30 +02:00
store . BaseClientPayload . UserAgent . OsVersion = proto . String ( br . WAVersion )
store . BaseClientPayload . UserAgent . OsBuildNumber = proto . String ( br . WAVersion )
2022-05-23 23:08:28 +02:00
store . DeviceProps . Os = proto . String ( br . Config . WhatsApp . OSName )
store . DeviceProps . RequireFullSync = proto . Bool ( br . Config . Bridge . HistorySync . RequestFullSync )
2023-01-04 21:37:25 +01:00
if fsc := br . Config . Bridge . HistorySync . FullSyncConfig ; fsc . DaysLimit > 0 && fsc . SizeLimit > 0 && fsc . StorageQuota > 0 {
store . DeviceProps . HistorySyncConfig = & waProto . DeviceProps_HistorySyncConfig {
FullSyncDaysLimit : proto . Uint32 ( fsc . DaysLimit ) ,
FullSyncSizeMbLimit : proto . Uint32 ( fsc . SizeLimit ) ,
StorageQuotaMb : proto . Uint32 ( fsc . StorageQuota ) ,
}
}
2022-05-22 00:06:30 +02:00
versionParts := strings . Split ( br . WAVersion , "." )
2021-10-22 19:14:34 +02:00
if len ( versionParts ) > 2 {
primary , _ := strconv . Atoi ( versionParts [ 0 ] )
secondary , _ := strconv . Atoi ( versionParts [ 1 ] )
tertiary , _ := strconv . Atoi ( versionParts [ 2 ] )
2022-05-23 23:08:28 +02:00
store . DeviceProps . Version . Primary = proto . Uint32 ( uint32 ( primary ) )
store . DeviceProps . Version . Secondary = proto . Uint32 ( uint32 ( secondary ) )
store . DeviceProps . Version . Tertiary = proto . Uint32 ( uint32 ( tertiary ) )
2021-10-22 19:14:34 +02:00
}
2022-07-30 10:30:44 +02:00
platformID , ok := waProto . DeviceProps_PlatformType_value [ strings . ToUpper ( br . Config . WhatsApp . BrowserName ) ]
2021-10-22 19:14:34 +02:00
if ok {
2022-07-30 10:30:44 +02:00
store . DeviceProps . PlatformType = waProto . DeviceProps_PlatformType ( platformID ) . Enum ( )
2021-10-22 19:14:34 +02:00
}
2018-08-13 22:24:44 +02:00
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) Start ( ) {
err := br . WAContainer . Upgrade ( )
if err != nil {
br . Log . Fatalln ( "Failed to upgrade whatsmeow database: %v" , err )
2018-08-29 22:48:15 +02:00
os . Exit ( 15 )
2018-08-18 21:57:08 +02:00
}
2022-05-22 00:06:30 +02:00
if br . Provisioning != nil {
br . Log . Debugln ( "Initializing provisioning API" )
br . Provisioning . Init ( )
2020-02-09 19:32:14 +01:00
}
2022-05-22 00:06:30 +02:00
go br . CheckWhatsAppUpdate ( )
go br . StartUsers ( )
if br . Config . Metrics . Enabled {
go br . Metrics . Start ( )
2020-06-17 16:50:06 +02:00
}
2020-06-15 19:28:04 +02:00
2022-05-22 00:06:30 +02:00
go br . Loop ( )
2020-06-15 19:28:04 +02:00
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) CheckWhatsAppUpdate ( ) {
br . Log . Debugfln ( "Checking for WhatsApp web update" )
2022-03-12 19:05:57 +01:00
resp , err := whatsmeow . CheckUpdate ( http . DefaultClient )
if err != nil {
2022-05-22 00:06:30 +02:00
br . Log . Warnfln ( "Failed to check for WhatsApp web update: %v" , err )
2022-03-12 19:05:57 +01:00
return
}
if store . GetWAVersion ( ) == resp . ParsedVersion {
2022-05-22 00:06:30 +02:00
br . Log . Debugfln ( "Bridge is using latest WhatsApp web protocol" )
2022-03-12 19:05:57 +01:00
} else if store . GetWAVersion ( ) . LessThan ( resp . ParsedVersion ) {
2022-03-23 13:46:30 +01:00
if resp . IsBelowHard || resp . IsBroken {
2022-05-22 00:06:30 +02:00
br . Log . Warnfln ( "Bridge is using outdated WhatsApp web protocol and probably doesn't work anymore (%s, latest is %s)" , store . GetWAVersion ( ) , resp . ParsedVersion )
2022-03-23 13:46:30 +01:00
} else if resp . IsBelowSoft {
2022-05-22 00:06:30 +02:00
br . 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 {
2022-05-22 00:06:30 +02:00
br . Log . Debugfln ( "Bridge is using outdated WhatsApp web protocol (%s, latest is %s)" , store . GetWAVersion ( ) , resp . ParsedVersion )
2022-03-12 19:05:57 +01:00
}
} else {
2022-05-22 00:06:30 +02:00
br . Log . Debugfln ( "Bridge is using newer than latest WhatsApp web protocol" )
2022-03-12 19:05:57 +01:00
}
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) Loop ( ) {
2022-01-25 13:26:24 +01:00
for {
2022-05-22 00:06:30 +02:00
br . SleepAndDeleteUpcoming ( )
2022-01-25 13:26:24 +01:00
time . Sleep ( 1 * time . Hour )
2022-05-22 00:06:30 +02:00
br . WarnUsersAboutDisconnection ( )
2022-01-25 13:26:24 +01:00
}
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) WarnUsersAboutDisconnection ( ) {
br . usersLock . Lock ( )
for _ , user := range br . 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 ( )
}
}
2022-05-22 00:06:30 +02:00
br . usersLock . Unlock ( )
2022-01-25 13:26:24 +01:00
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) StartUsers ( ) {
br . Log . Debugln ( "Starting users" )
2021-08-04 15:14:26 +02:00
foundAnySessions := false
2022-05-22 00:06:30 +02:00
for _ , user := range br . 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 {
2022-08-15 15:36:28 +02:00
br . SendGlobalBridgeState ( status . BridgeState { StateEvent : status . StateUnconfigured } . Fill ( nil ) )
2021-08-04 15:14:26 +02:00
}
2022-05-22 00:06:30 +02:00
br . Log . Debugln ( "Starting custom puppets" )
for _ , loopuppet := range br . GetAllPuppetsWithCustomMXID ( ) {
2019-05-31 22:07:33 +02:00
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
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) Stop ( ) {
br . Metrics . Stop ( )
for _ , user := range br . usersByUsername {
2021-10-22 19:14:34 +02:00
if user . Client == nil {
2019-05-23 21:57:19 +02:00
continue
}
2022-05-22 00:06:30 +02:00
br . 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
}
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) GetExampleConfig ( ) string {
return ExampleConfig
}
2021-11-07 21:31:22 +01:00
2022-05-22 00:06:30 +02:00
func ( br * WABridge ) GetConfigPtr ( ) interface { } {
br . Config = & config . Config {
BaseConfig : & br . Bridge . Config ,
2018-08-13 22:24:44 +02:00
}
2022-05-22 00:06:30 +02:00
br . Config . BaseConfig . Bridge = & br . Config . Bridge
return br . Config
2018-08-13 22:24:44 +02:00
}
2022-06-23 19:25:09 +02:00
const unstableFeatureBatchSending = "org.matrix.msc2716"
func ( br * WABridge ) CheckFeatures ( versions * mautrix . RespVersions ) ( string , bool ) {
if br . Config . Bridge . HistorySync . Backfill {
supported , known := versions . UnstableFeatures [ unstableFeatureBatchSending ]
if ! known {
2022-06-30 12:52:07 +02:00
return "Backfilling is enabled in bridge config, but homeserver does not support MSC2716 batch sending" , false
2022-06-23 19:25:09 +02:00
} else if ! supported {
2022-06-30 12:52:07 +02:00
return "Backfilling is enabled in bridge config, but MSC2716 batch sending is not enabled on homeserver" , false
2022-06-23 19:25:09 +02:00
}
}
return "" , true
}
2018-08-12 21:26:05 +02:00
func main ( ) {
2022-05-22 00:06:30 +02:00
br := & WABridge {
2021-11-07 21:31:22 +01:00
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 ) ,
2022-05-22 00:06:30 +02:00
}
br . Bridge = bridge . Bridge {
Name : "mautrix-whatsapp" ,
URL : "https://github.com/mautrix/whatsapp" ,
Description : "A Matrix-WhatsApp puppeting bridge." ,
2023-03-16 12:07:42 +01:00
Version : "0.8.3" ,
2022-05-22 00:06:30 +02:00
ProtocolName : "WhatsApp" ,
2022-05-31 16:28:58 +02:00
CryptoPickleKey : "maunium.net/go/mautrix-whatsapp" ,
2022-05-22 00:06:30 +02:00
ConfigUpgrader : & configupgrade . StructUpgrader {
SimpleUpgrader : configupgrade . SimpleUpgrader ( config . DoUpgrade ) ,
Blocks : config . SpacedBlocks ,
Base : ExampleConfig ,
} ,
Child : br ,
}
br . InitVersion ( Tag , Commit , BuildTime )
br . WAVersion = strings . FieldsFunc ( br . Version , func ( r rune ) bool { return r == '-' || r == '+' } ) [ 0 ]
br . Main ( )
2018-08-13 22:24:44 +02:00
}