2018-03-13 14:27:12 +01:00
// gomuks - A terminal Matrix client written in Go.
2020-04-19 17:10:14 +02:00
// Copyright (C) 2020 Tulir Asokan
2018-03-13 14:27:12 +01:00
//
// This program is free software: you can redistribute it and/or modify
2019-01-17 13:13:25 +01:00
// it under the terms of the GNU Affero General Public License as published by
2018-03-13 14:27:12 +01:00
// 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
2019-01-17 13:13:25 +01:00
// GNU Affero General Public License for more details.
2018-03-13 14:27:12 +01:00
//
2019-01-17 13:13:25 +01:00
// 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/>.
2018-03-13 14:27:12 +01:00
2018-03-18 20:24:03 +01:00
package matrix
2018-03-13 14:27:12 +01:00
import (
2023-10-05 22:33:21 +02:00
"bytes"
2020-02-18 20:31:28 +01:00
"context"
2019-01-17 13:13:25 +01:00
"crypto/tls"
2020-04-19 14:00:49 +02:00
"encoding/gob"
2019-01-17 13:13:25 +01:00
"encoding/json"
2021-03-11 21:53:13 +01:00
"errors"
2018-03-13 14:27:12 +01:00
"fmt"
2018-04-10 18:31:28 +02:00
"io"
"io/ioutil"
2018-04-11 16:57:15 +02:00
"net/http"
2018-04-10 18:31:28 +02:00
"os"
"path"
"path/filepath"
2020-04-19 14:00:49 +02:00
"reflect"
2019-06-16 13:29:03 +02:00
"runtime"
dbg "runtime/debug"
2023-10-05 22:33:21 +02:00
"strings"
2019-06-19 21:27:48 +02:00
"time"
2020-02-19 21:12:38 +01:00
2019-01-17 13:13:25 +01:00
"maunium.net/go/mautrix"
2020-04-29 01:45:54 +02:00
"maunium.net/go/mautrix/crypto/attachment"
2020-04-16 18:27:35 +02:00
"maunium.net/go/mautrix/event"
2019-01-17 13:13:25 +01:00
"maunium.net/go/mautrix/format"
2020-04-16 18:27:35 +02:00
"maunium.net/go/mautrix/id"
2020-04-22 11:31:54 +02:00
"maunium.net/go/mautrix/pushrules"
2019-01-17 13:13:25 +01:00
2018-04-10 18:31:28 +02:00
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/debug"
2020-09-01 15:33:39 +02:00
ifc "maunium.net/go/gomuks/interface"
2020-04-22 11:31:54 +02:00
"maunium.net/go/gomuks/lib/open"
"maunium.net/go/gomuks/matrix/muksevt"
2018-04-10 18:31:28 +02:00
"maunium.net/go/gomuks/matrix/rooms"
2018-03-13 14:27:12 +01:00
)
2018-11-13 23:00:35 +01:00
// Container is a wrapper for a mautrix Client and some other stuff.
2018-03-21 22:29:58 +01:00
//
// It is used for all Matrix calls from the UI and Matrix event handlers.
2018-03-18 20:24:03 +01:00
type Container struct {
2018-11-13 23:00:35 +01:00
client * mautrix . Client
2020-05-07 10:56:21 +02:00
crypto ifc . Crypto
2018-04-01 08:53:00 +02:00
syncer * GomuksSyncer
2018-03-18 20:24:03 +01:00
gmx ifc . Gomuks
ui ifc . GomuksUI
config * config . Config
2019-04-05 22:44:17 +02:00
history * HistoryManager
2018-03-13 14:27:12 +01:00
running bool
stop chan bool
2018-03-14 23:14:39 +01:00
typing int64
2018-03-13 14:27:12 +01:00
}
2018-03-21 22:29:58 +01:00
// NewContainer creates a new Container for the given Gomuks instance.
func NewContainer ( gmx ifc . Gomuks ) * Container {
2018-03-18 20:24:03 +01:00
c := & Container {
2018-03-13 20:58:43 +01:00
config : gmx . Config ( ) ,
ui : gmx . UI ( ) ,
gmx : gmx ,
2018-03-13 14:27:12 +01:00
}
2018-03-13 20:58:43 +01:00
return c
}
2018-11-13 23:00:35 +01:00
// Client returns the underlying mautrix Client.
func ( c * Container ) Client ( ) * mautrix . Client {
2018-05-01 20:04:25 +02:00
return c . client
}
2018-11-13 23:00:35 +01:00
type mxLogger struct { }
func ( log mxLogger ) Debugfln ( message string , args ... interface { } ) {
debug . Printf ( "[Matrix] " + message , args ... )
}
2020-05-07 10:56:21 +02:00
func ( c * Container ) Crypto ( ) ifc . Crypto {
return c . crypto
2020-04-26 23:38:04 +02:00
}
2022-11-21 21:42:42 +01:00
var (
ErrNoHomeserver = errors . New ( "no homeserver entered" )
ErrServerOutdated = errors . New ( "homeserver is outdated" )
)
var MinSpecVersion = mautrix . SpecV11
var SkipVersionCheck = false
2018-11-13 23:00:35 +01:00
// InitClient initializes the mautrix client and connects to the homeserver specified in the config.
2022-11-21 21:42:42 +01:00
func ( c * Container ) InitClient ( isStartup bool ) error {
2018-03-13 14:27:12 +01:00
if len ( c . config . HS ) == 0 {
2022-11-21 21:42:42 +01:00
if isStartup {
return nil
}
return ErrNoHomeserver
2018-03-13 14:27:12 +01:00
}
2018-03-13 20:58:43 +01:00
if c . client != nil {
c . Stop ( )
c . client = nil
2020-04-26 23:38:04 +02:00
c . crypto = nil
2018-03-13 20:58:43 +01:00
}
2020-04-16 18:27:35 +02:00
var mxid id . UserID
var accessToken string
2018-05-17 15:29:15 +02:00
if len ( c . config . AccessToken ) > 0 {
accessToken = c . config . AccessToken
2018-03-21 22:29:58 +01:00
mxid = c . config . UserID
2018-03-13 14:27:12 +01:00
}
var err error
2018-11-13 23:00:35 +01:00
c . client , err = mautrix . NewClient ( c . config . HS , mxid , accessToken )
2018-03-13 14:27:12 +01:00
if err != nil {
2020-10-23 11:02:42 +02:00
return fmt . Errorf ( "failed to create mautrix client: %w" , err )
2018-03-13 14:27:12 +01:00
}
2022-04-17 20:21:10 +02:00
c . client . UserAgent = fmt . Sprintf ( "gomuks/%s %s" , c . gmx . Version ( ) , mautrix . DefaultUserAgent )
2018-11-13 23:00:35 +01:00
c . client . Logger = mxLogger { }
2020-04-26 23:38:04 +02:00
c . client . DeviceID = c . config . DeviceID
2020-05-05 19:38:58 +02:00
err = c . initCrypto ( )
2020-04-26 23:38:04 +02:00
if err != nil {
2020-10-23 11:02:42 +02:00
return fmt . Errorf ( "failed to initialize crypto: %w" , err )
2020-04-26 23:38:04 +02:00
}
2018-03-13 14:27:12 +01:00
2020-02-19 21:12:38 +01:00
if c . history == nil {
c . history , err = NewHistoryManager ( c . config . HistoryPath )
if err != nil {
2020-10-23 11:02:42 +02:00
return fmt . Errorf ( "failed to initialize history: %w" , err )
2020-02-19 21:12:38 +01:00
}
2019-04-05 22:44:17 +02:00
}
2018-05-23 00:16:05 +02:00
allowInsecure := len ( os . Getenv ( "GOMUKS_ALLOW_INSECURE_CONNECTIONS" ) ) > 0
2018-05-23 00:05:56 +02:00
if allowInsecure {
c . client . Client = & http . Client {
Transport : & http . Transport { TLSClientConfig : & tls . Config { InsecureSkipVerify : true } } ,
}
}
2022-11-21 21:42:42 +01:00
if ! SkipVersionCheck && ( ! isStartup || len ( c . client . AccessToken ) > 0 ) {
debug . Printf ( "Checking versions that %s supports" , c . client . HomeserverURL )
resp , err := c . client . Versions ( )
if err != nil {
debug . Print ( "Error checking supported versions:" , err )
return fmt . Errorf ( "failed to check server versions: %w" , err )
} else if ! resp . ContainsGreaterOrEqual ( MinSpecVersion ) {
debug . Print ( "Server doesn't support modern spec versions" )
bestVersionStr := "nothing"
bestVersion := mautrix . MustParseSpecVersion ( "r0.0.0" )
for _ , ver := range resp . Versions {
if ver . GreaterThan ( bestVersion ) {
bestVersion = ver
bestVersionStr = ver . String ( )
}
}
return fmt . Errorf ( "%w (it only supports %s, while gomuks requires %s)" , ErrServerOutdated , bestVersionStr , MinSpecVersion . String ( ) )
} else {
debug . Print ( "Server supports modern spec versions" )
}
}
2018-03-13 14:27:12 +01:00
c . stop = make ( chan bool , 1 )
2018-05-17 15:29:15 +02:00
if len ( accessToken ) > 0 {
2018-03-13 14:27:12 +01:00
go c . Start ( )
}
return nil
}
2018-11-13 23:00:35 +01:00
// Initialized returns whether or not the mautrix client is initialized (see InitClient())
2018-03-18 20:24:03 +01:00
func ( c * Container ) Initialized ( ) bool {
2018-03-13 20:58:43 +01:00
return c . client != nil
}
2020-02-18 20:31:28 +01:00
func ( c * Container ) PasswordLogin ( user , password string ) error {
2018-11-13 23:00:35 +01:00
resp , err := c . client . Login ( & mautrix . ReqLogin {
2020-02-18 19:38:35 +01:00
Type : "m.login.password" ,
Identifier : mautrix . UserIdentifier {
Type : "m.id.user" ,
User : user ,
} ,
2018-04-16 20:30:34 +02:00
Password : password ,
InitialDeviceDisplayName : "gomuks" ,
2020-07-24 20:47:22 +02:00
2021-09-20 03:34:47 +02:00
StoreCredentials : true ,
StoreHomeserverURL : true ,
2018-03-13 14:27:12 +01:00
} )
if err != nil {
return err
}
2020-02-18 20:31:28 +01:00
c . finishLogin ( resp )
return nil
}
func ( c * Container ) finishLogin ( resp * mautrix . RespLogin ) {
2018-03-21 22:29:58 +01:00
c . config . UserID = resp . UserID
2020-04-26 23:38:04 +02:00
c . config . DeviceID = resp . DeviceID
2018-05-17 15:29:15 +02:00
c . config . AccessToken = resp . AccessToken
2021-09-20 03:34:47 +02:00
if resp . WellKnown != nil && len ( resp . WellKnown . Homeserver . BaseURL ) > 0 {
c . config . HS = resp . WellKnown . Homeserver . BaseURL
}
2018-03-13 14:27:12 +01:00
c . config . Save ( )
go c . Start ( )
2020-02-18 20:31:28 +01:00
}
2018-03-13 14:27:12 +01:00
2020-02-18 20:31:28 +01:00
func respondHTML ( w http . ResponseWriter , status int , message string ) {
w . Header ( ) . Add ( "Content-Type" , "text/html" )
w . WriteHeader ( status )
_ , _ = w . Write ( [ ] byte ( fmt . Sprintf ( ` < ! DOCTYPE html >
< html >
< head >
< title > gomuks single - sign on < / title >
< meta charset = "utf-8" / >
< / head >
< body >
< center >
< h2 > % s < / h2 >
< / center >
< / body >
< / html > ` , message ) ) )
}
func ( c * Container ) SingleSignOn ( ) error {
2022-04-17 12:16:47 +02:00
loginURL := c . client . BuildURLWithQuery ( mautrix . ClientURLPath { "v3" , "login" , "sso" , "redirect" } , map [ string ] string {
2020-02-18 20:31:28 +01:00
"redirectUrl" : "http://localhost:29325" ,
} )
err := open . Open ( loginURL )
if err != nil {
return err
}
errChan := make ( chan error , 1 )
server := & http . Server { Addr : ":29325" }
server . Handler = http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
loginToken := r . URL . Query ( ) . Get ( "loginToken" )
if len ( loginToken ) == 0 {
respondHTML ( w , http . StatusBadRequest , "Missing loginToken parameter" )
return
}
resp , err := c . client . Login ( & mautrix . ReqLogin {
Type : "m.login.token" ,
Token : loginToken ,
InitialDeviceDisplayName : "gomuks" ,
2020-07-24 20:47:22 +02:00
2021-09-20 03:34:47 +02:00
StoreCredentials : true ,
StoreHomeserverURL : true ,
2020-02-18 20:31:28 +01:00
} )
if err != nil {
respondHTML ( w , http . StatusForbidden , err . Error ( ) )
errChan <- err
return
}
respondHTML ( w , http . StatusOK , fmt . Sprintf ( "Successfully logged in as %s" , resp . UserID ) )
c . finishLogin ( resp )
go func ( ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
err = server . Shutdown ( ctx )
if err != nil {
debug . Printf ( "Failed to shut down SSO server: %v\n" , err )
}
errChan <- err
} ( )
} )
err = server . ListenAndServe ( )
if err != nil {
return err
}
2020-02-19 00:14:02 +01:00
err = <- errChan
2020-02-18 20:31:28 +01:00
return err
}
// Login sends a password login request with the given username and password.
func ( c * Container ) Login ( user , password string ) error {
resp , err := c . client . GetLoginFlows ( )
if err != nil {
return err
}
2022-11-07 17:39:06 +01:00
ssoSkippedBecausePassword := false
2020-09-18 20:12:59 +02:00
for _ , flow := range resp . Flows {
if flow . Type == "m.login.password" {
return c . PasswordLogin ( user , password )
2022-11-07 17:39:06 +01:00
} else if flow . Type == "m.login.sso" {
if len ( password ) == 0 {
return c . SingleSignOn ( )
} else {
ssoSkippedBecausePassword = true
}
2020-09-18 20:12:59 +02:00
}
2020-02-18 20:31:28 +01:00
}
2022-11-07 17:39:06 +01:00
if ssoSkippedBecausePassword {
return fmt . Errorf ( "password login is not supported\nleave the password field blank to use SSO" )
}
2020-09-18 20:12:59 +02:00
return fmt . Errorf ( "no supported login flows" )
2018-03-13 14:27:12 +01:00
}
2018-05-10 14:47:24 +02:00
// Logout revokes the access token, stops the syncer and calls the OnLogout() method of the UI.
func ( c * Container ) Logout ( ) {
c . client . Logout ( )
c . Stop ( )
2020-04-28 21:00:37 +02:00
c . config . DeleteSession ( )
2018-05-10 14:47:24 +02:00
c . client = nil
2020-04-26 23:38:04 +02:00
c . crypto = nil
2018-05-10 14:47:24 +02:00
c . ui . OnLogout ( )
}
2018-03-21 22:29:58 +01:00
// Stop stops the Matrix syncer.
2018-03-18 20:24:03 +01:00
func ( c * Container ) Stop ( ) {
2018-03-15 20:28:21 +01:00
if c . running {
2018-04-24 15:51:40 +02:00
debug . Print ( "Stopping Matrix container..." )
2020-05-06 19:11:35 +02:00
select {
case c . stop <- true :
default :
}
2018-03-15 20:28:21 +01:00
c . client . StopSync ( )
2019-04-05 22:44:17 +02:00
debug . Print ( "Closing history manager..." )
err := c . history . Close ( )
if err != nil {
debug . Print ( "Error closing history manager:" , err )
}
2020-02-19 22:48:34 +01:00
c . history = nil
2020-05-05 19:38:58 +02:00
if c . crypto != nil {
debug . Print ( "Flushing crypto store" )
err = c . crypto . FlushStore ( )
if err != nil {
debug . Print ( "Error flushing crypto store:" , err )
}
2020-04-28 21:00:37 +02:00
}
2018-03-15 20:28:21 +01:00
}
2018-03-13 20:58:43 +01:00
}
2018-03-21 22:29:58 +01:00
// UpdatePushRules fetches the push notification rules from the server and stores them in the current Session object.
2018-03-20 22:34:43 +01:00
func ( c * Container ) UpdatePushRules ( ) {
2018-03-21 19:01:52 +01:00
debug . Print ( "Updating push rules..." )
2020-04-16 18:27:35 +02:00
resp , err := c . client . GetPushRules ( )
2018-03-20 22:34:43 +01:00
if err != nil {
debug . Print ( "Failed to fetch push rules:" , err )
2019-04-27 14:02:21 +02:00
c . config . PushRules = & pushrules . PushRuleset { }
} else {
c . config . PushRules = resp
2018-03-20 22:34:43 +01:00
}
2018-05-17 15:29:15 +02:00
c . config . SavePushRules ( )
2018-03-20 22:34:43 +01:00
}
2018-03-21 22:29:58 +01:00
// PushRules returns the push notification rules. If no push rules are cached, UpdatePushRules() will be called first.
func ( c * Container ) PushRules ( ) * pushrules . PushRuleset {
2018-05-17 15:29:15 +02:00
if c . config . PushRules == nil {
2018-03-21 17:46:19 +01:00
c . UpdatePushRules ( )
}
2018-05-17 15:29:15 +02:00
return c . config . PushRules
2018-03-21 17:46:19 +01:00
}
2020-04-16 18:27:35 +02:00
var AccountDataGomuksPreferences = event . Type {
Type : "net.maunium.gomuks.preferences" ,
Class : event . AccountDataEventType ,
}
2018-09-05 09:55:48 +02:00
2020-04-19 14:00:49 +02:00
func init ( ) {
event . TypeMap [ AccountDataGomuksPreferences ] = reflect . TypeOf ( config . UserPreferences { } )
gob . Register ( & config . UserPreferences { } )
}
2020-04-19 17:06:45 +02:00
type StubSyncingModal struct { }
2020-04-22 11:31:54 +02:00
func ( s StubSyncingModal ) SetIndeterminate ( ) { }
2020-04-19 17:06:45 +02:00
func ( s StubSyncingModal ) SetMessage ( s2 string ) { }
2020-04-22 11:31:54 +02:00
func ( s StubSyncingModal ) SetSteps ( i int ) { }
func ( s StubSyncingModal ) Step ( ) { }
func ( s StubSyncingModal ) Close ( ) { }
2020-04-19 17:06:45 +02:00
2018-03-21 22:29:58 +01:00
// OnLogin initializes the syncer and updates the room list.
2018-03-18 20:24:03 +01:00
func ( c * Container ) OnLogin ( ) {
2021-02-08 21:42:40 +01:00
c . cryptoOnLogin ( )
2018-05-01 18:17:57 +02:00
c . ui . OnLogin ( )
2018-05-17 15:29:15 +02:00
c . client . Store = c . config
2018-03-13 20:58:43 +01:00
2018-04-24 15:51:40 +02:00
debug . Print ( "Initializing syncer" )
2020-04-19 17:06:45 +02:00
c . syncer = NewGomuksSyncer ( c . config . Rooms )
2020-05-05 19:38:58 +02:00
if c . crypto != nil {
c . syncer . OnSync ( c . crypto . ProcessSyncResponse )
2020-06-17 12:27:22 +02:00
c . syncer . OnEventType ( event . StateMember , func ( source mautrix . EventSource , evt * event . Event ) {
2020-05-05 20:15:53 +02:00
// Don't spam the crypto module with member events of an initial sync
// TODO invalidate all group sessions when clearing cache?
if c . config . AuthCache . InitialSyncDone {
c . crypto . HandleMemberEvent ( evt )
}
2020-05-05 19:38:58 +02:00
} )
c . syncer . OnEventType ( event . EventEncrypted , c . HandleEncrypted )
} else {
2020-06-23 21:28:28 +02:00
c . syncer . OnEventType ( event . EventEncrypted , c . HandleEncryptedUnsupported )
2020-05-05 19:38:58 +02:00
}
2020-04-16 18:27:35 +02:00
c . syncer . OnEventType ( event . EventMessage , c . HandleMessage )
c . syncer . OnEventType ( event . EventSticker , c . HandleMessage )
c . syncer . OnEventType ( event . EventReaction , c . HandleMessage )
c . syncer . OnEventType ( event . EventRedaction , c . HandleRedaction )
c . syncer . OnEventType ( event . StateAliases , c . HandleMessage )
c . syncer . OnEventType ( event . StateCanonicalAlias , c . HandleMessage )
c . syncer . OnEventType ( event . StateTopic , c . HandleMessage )
c . syncer . OnEventType ( event . StateRoomName , c . HandleMessage )
c . syncer . OnEventType ( event . StateMember , c . HandleMembership )
c . syncer . OnEventType ( event . EphemeralEventReceipt , c . HandleReadReceipt )
c . syncer . OnEventType ( event . EphemeralEventTyping , c . HandleTyping )
c . syncer . OnEventType ( event . AccountDataDirectChats , c . HandleDirectChatInfo )
c . syncer . OnEventType ( event . AccountDataPushRules , c . HandlePushRules )
c . syncer . OnEventType ( event . AccountDataRoomTags , c . HandleTag )
2018-09-05 09:55:48 +02:00
c . syncer . OnEventType ( AccountDataGomuksPreferences , c . HandlePreferences )
2020-04-19 17:56:36 +02:00
if len ( c . config . AuthCache . NextBatch ) == 0 {
c . syncer . Progress = c . ui . MainView ( ) . OpenSyncingModal ( )
c . syncer . Progress . SetMessage ( "Waiting for /sync response from server" )
c . syncer . Progress . SetIndeterminate ( )
c . syncer . FirstDoneCallback = func ( ) {
c . syncer . Progress . Close ( )
c . syncer . Progress = StubSyncingModal { }
c . syncer . FirstDoneCallback = nil
}
2020-04-19 17:06:45 +02:00
}
2018-04-24 16:12:08 +02:00
c . syncer . InitDoneCallback = func ( ) {
2018-11-13 23:00:35 +01:00
debug . Print ( "Initial sync done" )
2018-05-17 15:29:15 +02:00
c . config . AuthCache . InitialSyncDone = true
2019-06-15 16:04:08 +02:00
debug . Print ( "Updating title caches" )
for _ , room := range c . config . Rooms . Map {
room . GetTitle ( )
}
debug . Print ( "Cleaning cached rooms from memory" )
c . config . Rooms . ForceClean ( )
debug . Print ( "Saving all data" )
c . config . SaveAll ( )
debug . Print ( "Adding rooms to UI" )
2019-06-15 00:11:51 +02:00
c . ui . MainView ( ) . SetRooms ( c . config . Rooms )
2018-04-24 16:12:08 +02:00
c . ui . Render ( )
2019-06-16 13:29:03 +02:00
// The initial sync can be a bit heavy, so we force run the GC here
// after cleaning up rooms from memory above.
debug . Print ( "Running GC" )
runtime . GC ( )
dbg . FreeOSMemory ( )
2018-04-24 16:12:08 +02:00
}
2018-04-01 08:53:00 +02:00
c . client . Syncer = c . syncer
2018-03-13 14:27:12 +01:00
2018-04-24 15:51:40 +02:00
debug . Print ( "Setting existing rooms" )
2018-05-17 15:29:15 +02:00
c . ui . MainView ( ) . SetRooms ( c . config . Rooms )
2018-04-24 15:51:40 +02:00
debug . Print ( "OnLogin() done." )
2018-03-18 16:34:42 +01:00
}
2018-03-21 22:29:58 +01:00
// Start moves the UI to the main view, calls OnLogin() and runs the syncer forever until stopped with Stop()
2018-03-18 20:24:03 +01:00
func ( c * Container ) Start ( ) {
2018-04-18 17:35:24 +02:00
defer debug . Recover ( )
2018-03-18 16:34:42 +01:00
c . OnLogin ( )
2018-03-17 14:48:31 +01:00
2018-03-20 11:16:32 +01:00
if c . client == nil {
return
}
2018-03-18 20:24:03 +01:00
debug . Print ( "Starting sync..." )
2018-03-17 14:48:31 +01:00
c . running = true
2021-09-20 04:15:55 +02:00
c . client . StreamSyncMinAge = 30 * time . Minute
2018-03-13 14:27:12 +01:00
for {
select {
case <- c . stop :
2018-03-18 20:24:03 +01:00
debug . Print ( "Stopping sync..." )
2018-03-13 14:27:12 +01:00
c . running = false
return
default :
2018-03-13 20:58:43 +01:00
if err := c . client . Sync ( ) ; err != nil {
2020-09-12 00:59:53 +02:00
if errors . Is ( err , mautrix . MUnknownToken ) {
2018-05-10 14:47:24 +02:00
debug . Print ( "Sync() errored with " , err , " -> logging out" )
2020-10-23 11:02:42 +02:00
// TODO support soft logout
2018-05-10 14:47:24 +02:00
c . Logout ( )
} else {
debug . Print ( "Sync() errored" , err )
}
2018-03-13 20:58:43 +01:00
} else {
2018-03-18 20:24:03 +01:00
debug . Print ( "Sync() returned without error" )
2018-03-13 14:27:12 +01:00
}
}
}
}
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandlePreferences ( source mautrix . EventSource , evt * event . Event ) {
if source & mautrix . EventSourceAccountData == 0 {
2019-04-07 22:21:48 +02:00
return
}
2018-05-24 22:26:57 +02:00
orig := c . config . Preferences
2019-04-07 22:21:48 +02:00
err := json . Unmarshal ( evt . Content . VeryRaw , & c . config . Preferences )
if err != nil {
debug . Print ( "Failed to parse updated preferences:" , err )
return
}
2022-04-17 12:16:47 +02:00
debug . Printf ( "Updated preferences: %#v -> %#v" , orig , c . config . Preferences )
2019-06-15 00:11:51 +02:00
if c . config . AuthCache . InitialSyncDone {
c . ui . HandleNewPreferences ( )
}
2018-05-24 22:26:57 +02:00
}
2020-04-08 14:30:29 +02:00
func ( c * Container ) Preferences ( ) * config . UserPreferences {
return & c . config . Preferences
}
2018-05-24 22:26:57 +02:00
func ( c * Container ) SendPreferencesToMatrix ( ) {
defer debug . Recover ( )
2022-04-17 12:16:47 +02:00
debug . Printf ( "Sending updated preferences: %#v" , c . config . Preferences )
err := c . client . SetAccountData ( AccountDataGomuksPreferences . Type , & c . config . Preferences )
2018-05-24 22:26:57 +02:00
if err != nil {
debug . Print ( "Failed to update preferences:" , err )
}
}
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleRedaction ( source mautrix . EventSource , evt * event . Event ) {
2019-06-16 19:42:13 +02:00
room := c . GetOrCreateRoom ( evt . RoomID )
2020-04-16 18:27:35 +02:00
var redactedEvt * muksevt . Event
err := c . history . Update ( room , evt . Redacts , func ( redacted * muksevt . Event ) error {
2019-06-16 19:42:13 +02:00
redacted . Unsigned . RedactedBecause = evt
redactedEvt = redacted
return nil
} )
if err != nil {
debug . Print ( "Failed to mark" , evt . Redacts , "as redacted:" , err )
2019-06-19 21:27:48 +02:00
return
} else if ! c . config . AuthCache . InitialSyncDone || ! room . Loaded ( ) {
2019-06-16 19:42:13 +02:00
return
}
2019-06-19 21:27:48 +02:00
roomView := c . ui . MainView ( ) . GetRoom ( evt . RoomID )
2019-06-16 19:42:13 +02:00
if roomView == nil {
debug . Printf ( "Failed to handle event %v: No room view found." , evt )
return
}
2019-06-17 12:46:02 +02:00
roomView . AddRedaction ( redactedEvt )
if c . syncer . FirstSyncDone {
c . ui . Render ( )
}
}
2020-09-04 18:37:00 +02:00
var ErrCantEditOthersMessage = errors . New ( "can't edit message sent by someone else" )
2020-04-16 18:27:35 +02:00
func ( c * Container ) HandleEdit ( room * rooms . Room , editsID id . EventID , editEvent * muksevt . Event ) {
var origEvt * muksevt . Event
err := c . history . Update ( room , editsID , func ( evt * muksevt . Event ) error {
2020-09-04 18:37:00 +02:00
if editEvent . Sender != evt . Sender {
return ErrCantEditOthersMessage
}
2019-06-17 12:46:02 +02:00
evt . Gomuks . Edits = append ( evt . Gomuks . Edits , editEvent )
origEvt = evt
return nil
} )
2020-09-04 18:37:00 +02:00
if err == ErrCantEditOthersMessage {
debug . Printf ( "Ignoring edit %s of %s by %s in %s: original event was sent by someone else" , editEvent . ID , editsID , editEvent . Sender , editEvent . RoomID )
return
} else if err != nil {
2019-06-17 12:46:02 +02:00
debug . Print ( "Failed to store edit in history db:" , err )
2019-06-19 21:27:48 +02:00
return
} else if ! c . config . AuthCache . InitialSyncDone || ! room . Loaded ( ) {
2019-06-17 12:46:02 +02:00
return
}
roomView := c . ui . MainView ( ) . GetRoom ( editEvent . RoomID )
if roomView == nil {
debug . Printf ( "Failed to handle edit event %v: No room view found." , editEvent )
return
}
roomView . AddEdit ( origEvt )
if c . syncer . FirstSyncDone {
c . ui . Render ( )
2019-06-16 19:42:13 +02:00
}
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) HandleReaction ( room * rooms . Room , reactsTo id . EventID , reactEvent * muksevt . Event ) {
2020-04-19 14:00:49 +02:00
rel := reactEvent . Content . AsReaction ( ) . RelatesTo
2020-04-16 18:27:35 +02:00
var origEvt * muksevt . Event
err := c . history . Update ( room , reactsTo , func ( evt * muksevt . Event ) error {
2020-02-20 20:56:03 +01:00
if evt . Unsigned . Relations . Annotations . Map == nil {
evt . Unsigned . Relations . Annotations . Map = make ( map [ string ] int )
}
val , _ := evt . Unsigned . Relations . Annotations . Map [ rel . Key ]
evt . Unsigned . Relations . Annotations . Map [ rel . Key ] = val + 1
origEvt = evt
return nil
} )
if err != nil {
debug . Print ( "Failed to store reaction in history db:" , err )
return
} else if ! c . config . AuthCache . InitialSyncDone || ! room . Loaded ( ) {
return
}
roomView := c . ui . MainView ( ) . GetRoom ( reactEvent . RoomID )
if roomView == nil {
debug . Printf ( "Failed to handle edit event %v: No room view found." , reactEvent )
return
}
roomView . AddReaction ( origEvt , rel . Key )
if c . syncer . FirstSyncDone {
c . ui . Render ( )
}
}
2020-06-23 21:28:28 +02:00
func ( c * Container ) HandleEncryptedUnsupported ( source mautrix . EventSource , mxEvent * event . Event ) {
mxEvent . Type = muksevt . EventEncryptionUnsupported
origContent , _ := mxEvent . Content . Parsed . ( * event . EncryptedEventContent )
mxEvent . Content . Parsed = muksevt . EncryptionUnsupportedContent { Original : origContent }
c . HandleMessage ( source , mxEvent )
}
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleEncrypted ( source mautrix . EventSource , mxEvent * event . Event ) {
2020-04-26 23:38:04 +02:00
evt , err := c . crypto . DecryptMegolmEvent ( mxEvent )
if err != nil {
2020-06-23 21:28:28 +02:00
debug . Printf ( "Failed to decrypt event %s: %v" , mxEvent . ID , err )
mxEvent . Type = muksevt . EventBadEncrypted
origContent , _ := mxEvent . Content . Parsed . ( * event . EncryptedEventContent )
mxEvent . Content . Parsed = & muksevt . BadEncryptedContent {
Original : origContent ,
Reason : err . Error ( ) ,
}
2020-05-05 19:38:58 +02:00
c . HandleMessage ( source , mxEvent )
2020-04-26 23:38:04 +02:00
return
}
2020-09-12 23:13:08 +02:00
if evt . Type . IsInRoomVerification ( ) {
err := c . crypto . ProcessInRoomVerification ( evt )
if err != nil {
debug . Printf ( "[Crypto/Error] Failed to process in-room verification event %s of type %s: %v" , evt . ID , evt . Type . String ( ) , err )
} else {
debug . Printf ( "[Crypto/Debug] Processed in-room verification event %s of type %s" , evt . ID , evt . Type . String ( ) )
}
} else {
c . HandleMessage ( source , evt )
}
2020-04-26 23:38:04 +02:00
}
2018-03-21 22:29:58 +01:00
// HandleMessage is the event handler for the m.room.message timeline event.
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleMessage ( source mautrix . EventSource , mxEvent * event . Event ) {
2019-06-17 11:27:31 +02:00
room := c . GetOrCreateRoom ( mxEvent . RoomID )
2020-06-17 12:27:22 +02:00
if source & mautrix . EventSourceLeave != 0 {
2019-06-15 16:04:08 +02:00
room . HasLeft = true
return
2020-06-17 12:27:22 +02:00
} else if source & mautrix . EventSourceState != 0 {
2018-04-30 21:28:29 +02:00
return
}
2019-06-15 00:11:51 +02:00
2020-05-05 18:37:35 +02:00
relatable , ok := mxEvent . Content . Parsed . ( event . Relatable )
2020-05-05 18:16:25 +02:00
if ok {
rel := relatable . GetRelatesTo ( )
if editID := rel . GetReplaceID ( ) ; len ( editID ) > 0 {
c . HandleEdit ( room , editID , muksevt . Wrap ( mxEvent ) )
return
} else if reactionID := rel . GetAnnotationID ( ) ; mxEvent . Type == event . EventReaction && len ( reactionID ) > 0 {
c . HandleReaction ( room , reactionID , muksevt . Wrap ( mxEvent ) )
return
}
2019-06-17 12:46:02 +02:00
}
2020-04-16 18:27:35 +02:00
events , err := c . history . Append ( room , [ ] * event . Event { mxEvent } )
2019-06-15 00:11:51 +02:00
if err != nil {
2019-06-17 11:27:31 +02:00
debug . Printf ( "Failed to add event %s to history: %v" , mxEvent . ID , err )
2019-06-15 00:11:51 +02:00
}
2019-06-17 11:27:31 +02:00
evt := events [ 0 ]
2019-06-15 00:11:51 +02:00
2019-06-15 16:51:36 +02:00
if ! c . config . AuthCache . InitialSyncDone {
2019-06-15 16:04:08 +02:00
room . LastReceivedMessage = time . Unix ( evt . Timestamp / 1000 , evt . Timestamp % 1000 * 1000 )
2019-06-15 00:11:51 +02:00
return
}
2018-03-26 16:22:47 +02:00
mainView := c . ui . MainView ( )
2018-04-24 15:51:40 +02:00
2018-03-26 16:22:47 +02:00
roomView := mainView . GetRoom ( evt . RoomID )
2018-03-23 00:00:13 +01:00
if roomView == nil {
2018-04-24 15:51:40 +02:00
debug . Printf ( "Failed to handle event %v: No room view found." , evt )
2018-03-23 00:00:13 +01:00
return
}
2019-06-15 16:51:36 +02:00
if ! room . Loaded ( ) {
2019-06-17 11:27:31 +02:00
pushRules := c . PushRules ( ) . GetActions ( room , evt . Event ) . Should ( )
2023-03-22 22:32:37 +01:00
if ! pushRules . Notify {
2019-06-15 16:51:36 +02:00
room . LastReceivedMessage = time . Unix ( evt . Timestamp / 1000 , evt . Timestamp % 1000 * 1000 )
2023-03-22 22:32:37 +01:00
room . AddUnread ( evt . ID , pushRules . Notify , pushRules . Highlight )
2019-06-15 16:51:36 +02:00
mainView . Bump ( room )
return
}
}
2019-06-17 12:46:02 +02:00
message := roomView . AddEvent ( evt )
2018-03-23 00:00:13 +01:00
if message != nil {
2019-06-15 00:11:51 +02:00
roomView . MxRoom ( ) . LastReceivedMessage = message . Time ( )
2020-04-16 18:47:09 +02:00
if c . syncer . FirstSyncDone && evt . Sender != c . config . UserID {
2019-06-17 11:27:31 +02:00
pushRules := c . PushRules ( ) . GetActions ( roomView . MxRoom ( ) , evt . Event ) . Should ( )
2018-04-09 22:45:54 +02:00
mainView . NotifyMessage ( roomView . MxRoom ( ) , message , pushRules )
2018-04-24 15:51:40 +02:00
c . ui . Render ( )
2018-04-01 08:53:00 +02:00
}
2018-04-24 15:51:40 +02:00
} else {
2020-04-19 14:00:49 +02:00
debug . Printf ( "Parsing event %s type %s %v from %s in %s failed (ParseEvent() returned nil)." , evt . ID , evt . Type . Repr ( ) , evt . Content . Raw , evt . Sender , evt . RoomID )
2018-03-17 14:48:31 +01:00
}
2018-03-13 20:58:43 +01:00
}
2018-05-22 16:23:54 +02:00
// HandleMembership is the event handler for the m.room.member state event.
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleMembership ( source mautrix . EventSource , evt * event . Event ) {
isLeave := source & mautrix . EventSourceLeave != 0
isTimeline := source & mautrix . EventSourceTimeline != 0
2019-06-15 16:04:08 +02:00
if isLeave {
c . GetOrCreateRoom ( evt . RoomID ) . HasLeft = true
}
2018-05-22 16:23:54 +02:00
isNonTimelineLeave := isLeave && ! isTimeline
if ! c . config . AuthCache . InitialSyncDone && isNonTimelineLeave {
return
2020-04-16 18:27:35 +02:00
} else if evt . StateKey != nil && id . UserID ( * evt . StateKey ) == c . config . UserID {
2018-05-22 16:23:54 +02:00
c . processOwnMembershipChange ( evt )
} else if ! isTimeline && ( ! c . config . AuthCache . InitialSyncDone || isLeave ) {
// We don't care about other users' membership events in the initial sync or chats we've left.
return
}
c . HandleMessage ( source , evt )
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) processOwnMembershipChange ( evt * event . Event ) {
2020-04-19 14:00:49 +02:00
membership := evt . Content . AsMember ( ) . Membership
2020-04-16 18:27:35 +02:00
prevMembership := event . MembershipLeave
2018-05-22 16:23:54 +02:00
if evt . Unsigned . PrevContent != nil {
2020-04-19 14:00:49 +02:00
prevMembership = evt . Unsigned . PrevContent . AsMember ( ) . Membership
2018-05-22 16:23:54 +02:00
}
debug . Printf ( "Processing own membership change: %s->%s in %s" , prevMembership , membership , evt . RoomID )
if membership == prevMembership {
return
}
room := c . GetRoom ( evt . RoomID )
switch membership {
case "join" :
2020-02-22 00:30:43 +01:00
room . HasLeft = false
2020-04-04 00:23:06 +02:00
if c . config . AuthCache . InitialSyncDone {
c . ui . MainView ( ) . UpdateTags ( room )
}
2020-02-22 00:30:43 +01:00
fallthrough
case "invite" :
2019-06-15 00:11:51 +02:00
if c . config . AuthCache . InitialSyncDone {
c . ui . MainView ( ) . AddRoom ( room )
}
2018-05-22 16:23:54 +02:00
case "leave" :
2020-04-04 00:23:06 +02:00
case "ban" :
2019-06-15 00:11:51 +02:00
if c . config . AuthCache . InitialSyncDone {
c . ui . MainView ( ) . RemoveRoom ( room )
}
2018-05-22 16:23:54 +02:00
room . HasLeft = true
2019-06-15 00:11:51 +02:00
room . Unload ( )
2020-02-22 00:30:43 +01:00
default :
return
2018-05-22 16:23:54 +02:00
}
2020-02-22 00:30:43 +01:00
c . ui . Render ( )
2018-05-22 16:23:54 +02:00
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) parseReadReceipt ( evt * event . Event ) ( largestTimestampEvent id . EventID ) {
2018-05-16 19:09:09 +02:00
var largestTimestamp int64
2020-05-06 22:30:08 +02:00
for eventID , receipts := range * evt . Content . AsReceipt ( ) {
myInfo , ok := receipts . Read [ c . config . UserID ]
2018-05-16 19:09:09 +02:00
if ! ok {
continue
}
2020-05-06 22:30:08 +02:00
if myInfo . Timestamp > largestTimestamp {
largestTimestamp = myInfo . Timestamp
largestTimestampEvent = eventID
2018-05-16 19:09:09 +02:00
}
}
return
}
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleReadReceipt ( source mautrix . EventSource , evt * event . Event ) {
if source & mautrix . EventSourceLeave != 0 {
2018-05-16 19:09:09 +02:00
return
}
lastReadEvent := c . parseReadReceipt ( evt )
if len ( lastReadEvent ) == 0 {
return
}
room := c . GetRoom ( evt . RoomID )
2019-06-15 00:11:51 +02:00
if room != nil {
room . MarkRead ( lastReadEvent )
if c . config . AuthCache . InitialSyncDone {
c . ui . Render ( )
}
}
2018-05-16 19:09:09 +02:00
}
2020-09-12 23:13:08 +02:00
func ( c * Container ) parseDirectChatInfo ( evt * event . Event ) map [ * rooms . Room ] id . UserID {
directChats := make ( map [ * rooms . Room ] id . UserID )
for userID , roomIDList := range * evt . Content . AsDirectChats ( ) {
2020-05-06 22:30:08 +02:00
for _ , roomID := range roomIDList {
// TODO we shouldn't create direct chat rooms that we aren't in
room := c . GetOrCreateRoom ( roomID )
2018-05-16 19:09:09 +02:00
if room != nil && ! room . HasLeft {
2020-09-12 23:13:08 +02:00
directChats [ room ] = userID
2018-05-16 19:09:09 +02:00
}
}
}
return directChats
}
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleDirectChatInfo ( _ mautrix . EventSource , evt * event . Event ) {
2018-05-16 19:09:09 +02:00
directChats := c . parseDirectChatInfo ( evt )
2019-06-15 00:11:51 +02:00
for _ , room := range c . config . Rooms . Map {
2020-09-12 23:13:08 +02:00
userID , isDirect := directChats [ room ]
if isDirect != room . IsDirect {
room . IsDirect = isDirect
room . OtherUser = userID
2019-06-15 00:11:51 +02:00
if c . config . AuthCache . InitialSyncDone {
c . ui . MainView ( ) . UpdateTags ( room )
}
2018-05-16 19:09:09 +02:00
}
}
}
2018-03-21 22:29:58 +01:00
// HandlePushRules is the event handler for the m.push_rules account data event.
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandlePushRules ( _ mautrix . EventSource , evt * event . Event ) {
2018-03-21 19:01:52 +01:00
debug . Print ( "Received updated push rules" )
var err error
2018-05-17 15:29:15 +02:00
c . config . PushRules , err = pushrules . EventToPushRules ( evt )
2018-03-21 19:01:52 +01:00
if err != nil {
debug . Print ( "Failed to convert event to push rules:" , err )
2018-05-17 15:29:15 +02:00
return
2018-03-21 19:01:52 +01:00
}
2018-05-17 15:29:15 +02:00
c . config . SavePushRules ( )
2018-03-21 19:01:52 +01:00
}
2018-03-25 19:30:34 +02:00
// HandleTag is the event handler for the m.tag account data event.
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleTag ( _ mautrix . EventSource , evt * event . Event ) {
2019-06-15 16:04:08 +02:00
room := c . GetOrCreateRoom ( evt . RoomID )
2018-04-24 01:13:17 +02:00
2020-04-19 14:00:49 +02:00
tags := evt . Content . AsTag ( ) . Tags
newTags := make ( [ ] rooms . RoomTag , len ( tags ) )
2018-04-24 01:13:17 +02:00
index := 0
2020-04-19 14:00:49 +02:00
for tag , info := range tags {
2020-02-22 00:17:52 +01:00
order := json . Number ( "0.5" )
2018-09-05 09:55:48 +02:00
if len ( info . Order ) > 0 {
2020-02-22 00:17:52 +01:00
order = info . Order
2018-05-03 23:55:28 +02:00
}
2018-04-24 01:13:17 +02:00
newTags [ index ] = rooms . RoomTag {
Tag : tag ,
Order : order ,
}
index ++
}
2018-05-16 19:09:09 +02:00
room . RawTags = newTags
2019-06-15 00:11:51 +02:00
if c . config . AuthCache . InitialSyncDone {
mainView := c . ui . MainView ( )
mainView . UpdateTags ( room )
}
2018-03-25 19:30:34 +02:00
}
2018-03-21 22:29:58 +01:00
// HandleTyping is the event handler for the m.typing event.
2020-06-17 12:27:22 +02:00
func ( c * Container ) HandleTyping ( _ mautrix . EventSource , evt * event . Event ) {
2019-06-15 00:11:51 +02:00
if ! c . config . AuthCache . InitialSyncDone {
return
}
2020-04-19 14:00:49 +02:00
c . ui . MainView ( ) . SetTyping ( evt . RoomID , evt . Content . AsTyping ( ) . UserIDs )
2018-03-14 23:14:39 +01:00
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) MarkRead ( roomID id . RoomID , eventID id . EventID ) {
2020-05-10 00:23:20 +02:00
go func ( ) {
defer debug . Recover ( )
err := c . client . MarkRead ( roomID , eventID )
if err != nil {
2022-04-15 12:13:46 +02:00
debug . Printf ( "Failed to mark %s in %s as read: %v" , eventID , roomID , err )
2020-05-10 00:23:20 +02:00
}
} ( )
2018-05-16 19:09:09 +02:00
}
2020-09-02 01:12:09 +02:00
func ( c * Container ) PrepareMediaMessage ( room * rooms . Room , path string , rel * ifc . Relation ) ( * muksevt . Event , error ) {
resp , err := c . UploadMedia ( path , room . Encrypted )
if err != nil {
return nil , err
}
content := event . MessageEventContent {
2021-03-11 21:53:13 +01:00
MsgType : resp . MsgType ,
Body : resp . Name ,
Info : resp . Info ,
2020-09-02 01:12:09 +02:00
}
if resp . EncryptionInfo != nil {
content . File = & event . EncryptedFileInfo {
EncryptedFile : * resp . EncryptionInfo ,
URL : resp . ContentURI . CUString ( ) ,
}
} else {
content . URL = resp . ContentURI . CUString ( )
}
return c . prepareEvent ( room . ID , & content , rel ) , nil
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) PrepareMarkdownMessage ( roomID id . RoomID , msgtype event . MessageType , text , html string , rel * ifc . Relation ) * muksevt . Event {
2020-04-19 14:00:49 +02:00
var content event . MessageEventContent
2020-03-20 13:32:29 +01:00
if html != "" {
2020-04-19 14:00:49 +02:00
content = event . MessageEventContent {
2020-03-20 13:32:29 +01:00
FormattedBody : html ,
2020-04-16 18:27:35 +02:00
Format : event . FormatHTML ,
2020-03-20 13:32:29 +01:00
Body : text ,
MsgType : msgtype ,
}
} else {
2020-04-04 00:03:17 +02:00
content = format . RenderMarkdown ( text , ! c . config . Preferences . DisableMarkdown , ! c . config . Preferences . DisableHTML )
2020-03-20 13:32:29 +01:00
content . MsgType = msgtype
}
2018-04-18 13:20:57 +02:00
2020-09-02 01:12:09 +02:00
return c . prepareEvent ( roomID , & content , rel )
}
func ( c * Container ) prepareEvent ( roomID id . RoomID , content * event . MessageEventContent , rel * ifc . Relation ) * muksevt . Event {
2020-04-16 18:27:35 +02:00
if rel != nil && rel . Type == event . RelReplace {
2020-09-02 01:12:09 +02:00
contentCopy := * content
2020-02-19 00:14:02 +01:00
content . NewContent = & contentCopy
content . Body = "* " + content . Body
if len ( content . FormattedBody ) > 0 {
content . FormattedBody = "* " + content . FormattedBody
}
2020-04-16 18:27:35 +02:00
content . RelatesTo = & event . RelatesTo {
Type : event . RelReplace ,
2020-02-29 23:33:37 +01:00
EventID : rel . Event . ID ,
2020-02-19 00:14:02 +01:00
}
2020-11-11 22:42:29 +01:00
} else if rel != nil && rel . Type == event . RelReply {
2020-02-29 23:33:37 +01:00
content . SetReply ( rel . Event . Event )
2020-02-19 00:14:02 +01:00
}
2019-04-09 17:45:41 +02:00
txnID := c . client . TxnID ( )
2020-04-16 18:27:35 +02:00
localEcho := muksevt . Wrap ( & event . Event {
ID : id . EventID ( txnID ) ,
2019-04-09 17:45:41 +02:00
Sender : c . config . UserID ,
2020-04-16 18:27:35 +02:00
Type : event . EventMessage ,
2019-04-09 17:45:41 +02:00
Timestamp : time . Now ( ) . UnixNano ( ) / 1e6 ,
RoomID : roomID ,
2020-09-02 01:12:09 +02:00
Content : event . Content { Parsed : content } ,
2020-04-19 14:00:49 +02:00
Unsigned : event . Unsigned { TransactionID : txnID } ,
2019-06-17 11:27:31 +02:00
} )
2020-04-16 18:27:35 +02:00
localEcho . Gomuks . OutgoingState = muksevt . StateLocalEcho
if rel != nil && rel . Type == event . RelReplace {
2020-02-29 23:33:37 +01:00
localEcho . ID = rel . Event . ID
2020-04-16 18:27:35 +02:00
localEcho . Gomuks . Edits = [ ] * muksevt . Event { localEcho }
2020-02-19 00:14:02 +01:00
}
2019-04-10 00:04:39 +02:00
return localEcho
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) Redact ( roomID id . RoomID , eventID id . EventID , reason string ) error {
2020-03-01 21:35:21 +01:00
defer debug . Recover ( )
_ , err := c . client . RedactEvent ( roomID , eventID , mautrix . ReqRedact { Reason : reason } )
return err
}
// SendMessage sends the given event.
2020-04-19 14:00:49 +02:00
func ( c * Container ) SendEvent ( evt * muksevt . Event ) ( id . EventID , error ) {
2019-04-10 00:04:39 +02:00
defer debug . Recover ( )
2020-04-27 23:58:26 +02:00
_ , _ = c . client . UserTyping ( evt . RoomID , false , 0 )
2020-03-03 20:15:25 +01:00
c . typing = 0
2020-04-27 23:58:26 +02:00
room := c . GetRoom ( evt . RoomID )
2020-05-05 19:38:58 +02:00
if room != nil && room . Encrypted && c . crypto != nil && evt . Type != event . EventReaction {
2020-06-18 11:57:47 +02:00
encrypted , err := c . crypto . EncryptMegolmEvent ( evt . RoomID , evt . Type , & evt . Content )
2020-04-27 23:58:26 +02:00
if err != nil {
2020-05-05 19:38:58 +02:00
if isBadEncryptError ( err ) {
2020-04-27 23:58:26 +02:00
return "" , err
}
debug . Print ( "Got" , err , "while trying to encrypt message, sharing group session and trying again..." )
err = c . crypto . ShareGroupSession ( room . ID , room . GetMemberList ( ) )
if err != nil {
return "" , err
}
2020-06-18 11:57:47 +02:00
encrypted , err = c . crypto . EncryptMegolmEvent ( evt . RoomID , evt . Type , & evt . Content )
2020-04-27 23:58:26 +02:00
if err != nil {
return "" , err
}
}
evt . Type = event . EventEncrypted
evt . Content = event . Content { Parsed : encrypted }
}
2020-04-19 14:00:49 +02:00
resp , err := c . client . SendMessageEvent ( evt . RoomID , evt . Type , & evt . Content , mautrix . ReqSendEvent { TransactionID : evt . Unsigned . TransactionID } )
2018-04-18 13:20:57 +02:00
if err != nil {
return "" , err
}
return resp . EventID , nil
}
2023-10-05 22:33:21 +02:00
func ( c * Container ) UploadMedia ( media string , encrypt bool ) ( * ifc . UploadedMediaInfo , error ) {
var content io . Reader
var fileSize int64
var uploadFileName string
var uploadMimeType string
var msgtype event . MessageType
var info event . FileInfo
mime := http . DetectContentType ( [ ] byte ( media ) )
pasted := strings . HasPrefix ( mime , "image" )
if pasted {
debug . Print ( "An image was pasted" )
content = bytes . NewReader ( [ ] byte ( media ) )
fileSize = content . ( * bytes . Reader ) . Size ( )
msgtype = event . MsgImage
uploadMimeType = mime
uploadFileName = "image"
} else {
path , err := filepath . Abs ( media )
if err != nil {
return nil , fmt . Errorf ( "failed to get absolute path: %w" , err )
}
2020-09-02 01:12:09 +02:00
2023-10-05 22:33:21 +02:00
file , err := os . Open ( path )
if err != nil {
return nil , fmt . Errorf ( "failed to open file: %w" , err )
}
defer file . Close ( )
2020-09-01 15:33:39 +02:00
2023-10-05 22:33:21 +02:00
stat , err := file . Stat ( )
if err != nil {
return nil , fmt . Errorf ( "failed to get file info: %w" , err )
}
2020-09-01 15:33:39 +02:00
2023-10-05 22:33:21 +02:00
fileSize = stat . Size ( )
uploadFileName = stat . Name ( )
msgtype , info , err = getMediaInfo ( path )
if err != nil {
return nil , err
}
uploadMimeType = info . MimeType
content = file
2020-09-02 01:12:09 +02:00
}
var encryptionInfo * attachment . EncryptedFile
if encrypt {
2023-10-05 22:33:21 +02:00
encryptionInfo = attachment . NewEncryptedFile ( )
content = encryptionInfo . EncryptStream ( content )
2020-09-02 01:12:09 +02:00
uploadMimeType = "application/octet-stream"
uploadFileName = ""
}
resp , err := c . client . UploadMedia ( mautrix . ReqUploadMedia {
Content : content ,
2023-10-05 22:33:21 +02:00
ContentLength : fileSize ,
2020-09-02 01:12:09 +02:00
ContentType : uploadMimeType ,
FileName : uploadFileName ,
} )
2020-09-01 15:33:39 +02:00
if err != nil {
return nil , err
}
2020-09-02 01:12:09 +02:00
return & ifc . UploadedMediaInfo {
RespMediaUpload : resp ,
EncryptionInfo : encryptionInfo ,
2023-10-05 22:33:21 +02:00
Name : uploadFileName ,
2020-09-02 01:12:09 +02:00
MsgType : msgtype ,
Info : & info ,
} , nil
2020-09-01 15:33:39 +02:00
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) sendTypingAsync ( roomID id . RoomID , typing bool , timeout int64 ) {
2020-03-03 20:15:25 +01:00
defer debug . Recover ( )
_ , _ = c . client . UserTyping ( roomID , typing , timeout )
}
2018-03-21 22:29:58 +01:00
// SendTyping sets whether or not the user is typing in the given room.
2020-04-16 18:27:35 +02:00
func ( c * Container ) SendTyping ( roomID id . RoomID , typing bool ) {
2018-03-18 20:24:03 +01:00
ts := time . Now ( ) . Unix ( )
2020-03-03 20:15:25 +01:00
if ( c . typing > ts && typing ) || ( c . typing == 0 && ! typing ) {
2018-03-14 23:14:39 +01:00
return
}
2018-03-15 18:45:52 +01:00
if typing {
2020-03-03 20:15:25 +01:00
go c . sendTypingAsync ( roomID , true , 20000 )
2018-03-21 22:29:58 +01:00
c . typing = ts + 15
2018-03-15 18:45:52 +01:00
} else {
2020-03-03 20:15:25 +01:00
go c . sendTypingAsync ( roomID , false , 0 )
2018-03-15 18:45:52 +01:00
c . typing = 0
}
2018-03-14 23:14:39 +01:00
}
2019-06-14 03:14:32 +02:00
// CreateRoom attempts to create a new room and join the user.
func ( c * Container ) CreateRoom ( req * mautrix . ReqCreateRoom ) ( * rooms . Room , error ) {
resp , err := c . client . CreateRoom ( req )
if err != nil {
return nil , err
}
2019-06-15 00:11:51 +02:00
room := c . GetOrCreateRoom ( resp . RoomID )
2019-06-14 03:14:32 +02:00
return room , nil
}
2018-03-21 22:29:58 +01:00
// JoinRoom makes the current user try to join the given room.
2020-04-16 18:27:35 +02:00
func ( c * Container ) JoinRoom ( roomID id . RoomID , server string ) ( * rooms . Room , error ) {
resp , err := c . client . JoinRoom ( string ( roomID ) , server , nil )
2018-03-18 20:24:03 +01:00
if err != nil {
2018-04-30 22:09:14 +02:00
return nil , err
2018-03-18 20:24:03 +01:00
}
2020-02-18 20:41:49 +01:00
room := c . GetOrCreateRoom ( resp . RoomID )
2018-04-30 22:09:14 +02:00
room . HasLeft = false
return room , nil
2018-03-18 20:24:03 +01:00
}
2018-03-21 22:29:58 +01:00
// LeaveRoom makes the current user leave the given room.
2020-04-16 18:27:35 +02:00
func ( c * Container ) LeaveRoom ( roomID id . RoomID ) error {
2018-03-18 20:24:03 +01:00
_ , err := c . client . LeaveRoom ( roomID )
2018-03-16 15:24:11 +01:00
if err != nil {
return err
}
2019-06-15 00:11:51 +02:00
node := c . GetOrCreateRoom ( roomID )
node . HasLeft = true
node . Unload ( )
2018-03-16 15:24:11 +01:00
return nil
}
2020-02-21 23:03:57 +01:00
func ( c * Container ) FetchMembers ( room * rooms . Room ) error {
2020-04-20 21:46:41 +02:00
debug . Print ( "Fetching member list for" , room . ID )
2020-02-21 23:03:57 +01:00
members , err := c . client . Members ( room . ID , mautrix . ReqMembers { At : room . LastPrevBatch } )
if err != nil {
return err
}
2020-04-20 21:46:41 +02:00
debug . Printf ( "Fetched %d members for %s" , len ( members . Chunk ) , room . ID )
2020-02-21 23:03:57 +01:00
for _ , evt := range members . Chunk {
2020-04-20 21:46:41 +02:00
err := evt . Content . ParseRaw ( evt . Type )
if err != nil {
debug . Printf ( "Failed to parse member event of %s: %v" , evt . GetStateKey ( ) , err )
continue
}
2020-02-21 23:03:57 +01:00
room . UpdateState ( evt )
}
room . MembersFetched = true
return nil
}
2018-03-21 22:29:58 +01:00
// GetHistory fetches room history.
2020-05-10 01:28:32 +02:00
func ( c * Container ) GetHistory ( room * rooms . Room , limit int , dbPointer uint64 ) ( [ ] * muksevt . Event , uint64 , error ) {
events , newDBPointer , err := c . history . Load ( room , limit , dbPointer )
2018-03-18 16:34:42 +01:00
if err != nil {
2020-05-10 01:28:32 +02:00
return nil , dbPointer , err
2019-04-05 22:44:17 +02:00
}
if len ( events ) > 0 {
debug . Printf ( "Loaded %d events for %s from local cache" , len ( events ) , room . ID )
2020-05-10 01:28:32 +02:00
return events , newDBPointer , nil
2019-04-05 22:44:17 +02:00
}
2022-03-06 21:19:40 +01:00
resp , err := c . client . Messages ( room . ID , room . PrevBatch , "" , 'b' , nil , limit )
2019-04-05 22:44:17 +02:00
if err != nil {
2020-05-10 01:28:32 +02:00
return nil , dbPointer , err
2019-04-05 22:44:17 +02:00
}
debug . Printf ( "Loaded %d events for %s from server from %s to %s" , len ( resp . Chunk ) , room . ID , resp . Start , resp . End )
2020-05-05 17:39:28 +02:00
for i , evt := range resp . Chunk {
2020-04-19 14:00:49 +02:00
err := evt . Content . ParseRaw ( evt . Type )
if err != nil {
debug . Printf ( "Failed to unmarshal content of event %s (type %s) by %s in %s: %v\n%s" , evt . ID , evt . Type . Repr ( ) , evt . Sender , evt . RoomID , err , string ( evt . Content . VeryRaw ) )
}
2020-05-05 17:39:28 +02:00
2020-06-23 21:28:28 +02:00
if evt . Type == event . EventEncrypted {
if c . crypto == nil {
evt . Type = muksevt . EventEncryptionUnsupported
origContent , _ := evt . Content . Parsed . ( * event . EncryptedEventContent )
evt . Content . Parsed = muksevt . EncryptionUnsupportedContent { Original : origContent }
2020-05-05 17:39:28 +02:00
} else {
2020-06-23 21:28:28 +02:00
decrypted , err := c . crypto . DecryptMegolmEvent ( evt )
if err != nil {
debug . Printf ( "Failed to decrypt event %s: %v" , evt . ID , err )
evt . Type = muksevt . EventBadEncrypted
origContent , _ := evt . Content . Parsed . ( * event . EncryptedEventContent )
evt . Content . Parsed = & muksevt . BadEncryptedContent {
Original : origContent ,
Reason : err . Error ( ) ,
}
} else {
resp . Chunk [ i ] = decrypted
}
2020-05-05 17:39:28 +02:00
}
}
2020-04-19 14:00:49 +02:00
}
2020-02-21 23:03:57 +01:00
for _ , evt := range resp . State {
room . UpdateState ( evt )
}
2019-06-15 00:11:51 +02:00
room . PrevBatch = resp . End
c . config . Rooms . Put ( room )
2019-06-17 11:27:31 +02:00
if len ( resp . Chunk ) == 0 {
2020-05-10 01:28:32 +02:00
return [ ] * muksevt . Event { } , dbPointer , nil
2019-06-17 11:27:31 +02:00
}
2020-05-10 01:28:32 +02:00
// TODO newDBPointer isn't accurate in this case yet, fix later
events , newDBPointer , err = c . history . Prepend ( room , resp . Chunk )
2019-06-17 11:27:31 +02:00
if err != nil {
2020-05-10 01:28:32 +02:00
return nil , dbPointer , err
2019-06-17 11:27:31 +02:00
}
2020-05-10 01:28:32 +02:00
return events , dbPointer , nil
2019-04-05 22:44:17 +02:00
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) GetEvent ( room * rooms . Room , eventID id . EventID ) ( * muksevt . Event , error ) {
2019-06-17 11:27:31 +02:00
evt , err := c . history . Get ( room , eventID )
2019-06-19 21:27:48 +02:00
if err != nil && err != EventNotFoundError {
debug . Printf ( "Failed to get event %s from local cache: %v" , eventID , err )
} else if evt != nil {
2019-04-05 22:44:17 +02:00
debug . Printf ( "Found event %s in local cache" , eventID )
2019-06-17 11:27:31 +02:00
return evt , err
2019-04-05 22:44:17 +02:00
}
2019-06-17 11:27:31 +02:00
mxEvent , err := c . client . GetEvent ( room . ID , eventID )
2019-04-05 22:44:17 +02:00
if err != nil {
return nil , err
2018-03-18 16:34:42 +01:00
}
2020-05-06 22:40:50 +02:00
err = mxEvent . Content . ParseRaw ( mxEvent . Type )
if err != nil {
return nil , err
}
2019-04-05 22:44:17 +02:00
debug . Printf ( "Loaded event %s from server" , eventID )
2020-05-06 22:40:50 +02:00
return muksevt . Wrap ( mxEvent ) , nil
2018-03-18 16:34:42 +01:00
}
2019-06-15 00:11:51 +02:00
// GetOrCreateRoom gets the room instance stored in the session.
2020-04-16 18:27:35 +02:00
func ( c * Container ) GetOrCreateRoom ( roomID id . RoomID ) * rooms . Room {
2019-06-15 00:11:51 +02:00
return c . config . Rooms . GetOrCreate ( roomID )
}
2018-03-21 22:29:58 +01:00
// GetRoom gets the room instance stored in the session.
2020-04-16 18:27:35 +02:00
func ( c * Container ) GetRoom ( roomID id . RoomID ) * rooms . Room {
2019-06-15 00:11:51 +02:00
return c . config . Rooms . Get ( roomID )
2018-03-14 23:14:39 +01:00
}
2018-04-10 18:31:28 +02:00
2020-04-08 14:30:29 +02:00
func cp ( src , dst string ) error {
in , err := os . Open ( src )
if err != nil {
return err
}
defer in . Close ( )
2018-04-10 18:31:28 +02:00
2020-04-08 14:30:29 +02:00
out , err := os . Create ( dst )
if err != nil {
return err
}
defer out . Close ( )
_ , err = io . Copy ( out , in )
if err != nil {
return err
}
return out . Close ( )
}
2020-04-29 01:45:54 +02:00
func ( c * Container ) DownloadToDisk ( uri id . ContentURI , file * attachment . EncryptedFile , target string ) ( fullPath string , err error ) {
2020-04-08 14:30:29 +02:00
cachePath := c . GetCachePath ( uri )
if target == "" {
fullPath = cachePath
} else if ! path . IsAbs ( target ) {
fullPath = path . Join ( c . config . DownloadDir , target )
} else {
fullPath = target
}
if _ , statErr := os . Stat ( cachePath ) ; os . IsNotExist ( statErr ) {
2020-04-29 01:45:54 +02:00
var body io . ReadCloser
body , err = c . client . Download ( uri )
2020-04-08 14:30:29 +02:00
if err != nil {
return
}
2020-04-29 01:45:54 +02:00
var data [ ] byte
data , err = ioutil . ReadAll ( body )
_ = body . Close ( )
2020-04-08 14:30:29 +02:00
if err != nil {
return
}
2020-04-29 01:45:54 +02:00
if file != nil {
2022-11-13 16:59:11 +01:00
err = file . DecryptInPlace ( data )
2020-04-29 01:45:54 +02:00
if err != nil {
return
}
}
err = ioutil . WriteFile ( cachePath , data , 0600 )
2020-04-08 14:30:29 +02:00
if err != nil {
return
}
2018-04-10 18:31:28 +02:00
}
2018-04-15 13:03:05 +02:00
2020-04-08 14:30:29 +02:00
if fullPath != cachePath {
err = os . MkdirAll ( path . Dir ( fullPath ) , 0700 )
if err != nil {
return
}
err = cp ( cachePath , fullPath )
}
return
}
2018-04-10 18:31:28 +02:00
2020-04-08 14:30:29 +02:00
// Download fetches the given Matrix content (mxc) URL and returns the data, homeserver, file ID and potential errors.
//
// The file will be either read from the media cache (if found) or downloaded from the server.
2020-04-29 01:45:54 +02:00
func ( c * Container ) Download ( uri id . ContentURI , file * attachment . EncryptedFile ) ( data [ ] byte , err error ) {
2020-04-08 14:30:29 +02:00
cacheFile := c . GetCachePath ( uri )
2018-05-10 19:31:11 +02:00
var info os . FileInfo
if info , err = os . Stat ( cacheFile ) ; err == nil && ! info . IsDir ( ) {
2018-04-11 16:57:15 +02:00
data , err = ioutil . ReadFile ( cacheFile )
2018-04-10 18:31:28 +02:00
if err == nil {
2018-04-11 16:57:15 +02:00
return
2018-04-10 18:31:28 +02:00
}
}
2020-04-29 01:45:54 +02:00
data , err = c . download ( uri , file , cacheFile )
2018-04-15 13:03:05 +02:00
return
}
2020-04-16 18:27:35 +02:00
func ( c * Container ) GetDownloadURL ( uri id . ContentURI ) string {
2020-04-22 11:31:54 +02:00
return c . client . GetDownloadURL ( uri )
2018-05-22 21:06:48 +02:00
}
2018-04-10 18:31:28 +02:00
2020-04-29 01:45:54 +02:00
func ( c * Container ) download ( uri id . ContentURI , file * attachment . EncryptedFile , cacheFile string ) ( data [ ] byte , err error ) {
2020-04-22 11:31:54 +02:00
var body io . ReadCloser
body , err = c . client . Download ( uri )
2018-04-10 18:31:28 +02:00
if err != nil {
2018-04-11 16:57:15 +02:00
return
2018-04-10 18:31:28 +02:00
}
2020-04-29 01:45:54 +02:00
data , err = ioutil . ReadAll ( body )
_ = body . Close ( )
2018-04-10 18:31:28 +02:00
if err != nil {
2018-04-11 16:57:15 +02:00
return
2018-04-10 18:31:28 +02:00
}
2020-04-29 01:45:54 +02:00
if file != nil {
2022-11-13 16:59:11 +01:00
err = file . DecryptInPlace ( data )
2020-04-29 01:45:54 +02:00
if err != nil {
return
}
}
2018-04-10 18:31:28 +02:00
err = ioutil . WriteFile ( cacheFile , data , 0600 )
2018-04-11 16:57:15 +02:00
return
2018-04-10 18:31:28 +02:00
}
2018-04-11 18:20:40 +02:00
2018-05-01 18:17:57 +02:00
// GetCachePath gets the path to the cached version of the given homeserver:fileID combination.
// The file may or may not exist, use Download() to ensure it has been cached.
2020-04-16 18:27:35 +02:00
func ( c * Container ) GetCachePath ( uri id . ContentURI ) string {
2020-04-08 14:30:29 +02:00
dir := filepath . Join ( c . config . MediaDir , uri . Homeserver )
2018-04-10 18:31:28 +02:00
err := os . MkdirAll ( dir , 0700 )
if err != nil {
return ""
}
2020-04-08 14:30:29 +02:00
return filepath . Join ( dir , uri . FileID )
2018-04-10 18:31:28 +02:00
}