2023-02-22 01:48:49 +03:00
// SPDX-FileCopyrightText: Nheko Contributors
2021-03-05 02:35:15 +03:00
//
// SPDX-License-Identifier: GPL-3.0-or-later
2017-04-06 02:06:42 +03:00
2017-10-21 18:53:15 +03:00
# include <QApplication>
2020-12-18 05:04:18 +03:00
# include <QInputDialog>
2020-05-02 17:44:50 +03:00
# include <QMessageBox>
2017-04-06 02:06:42 +03:00
2023-10-24 02:12:01 +03:00
# include <algorithm>
# include <unordered_set>
2023-10-30 16:56:10 +03:00
# include <nlohmann/json.hpp>
2020-10-27 19:45:28 +03:00
# include <mtx/responses.hpp>
2017-08-26 13:49:16 +03:00
# include "AvatarProvider.h"
2017-10-28 15:46:39 +03:00
# include "Cache.h"
2019-12-15 04:56:04 +03:00
# include "Cache_p.h"
2017-04-06 02:06:42 +03:00
# include "ChatPage.h"
2020-06-17 21:28:35 +03:00
# include "EventAccessors.h"
2018-07-17 16:37:25 +03:00
# include "Logging.h"
2017-08-26 13:49:16 +03:00
# include "MainWindow.h"
2017-10-28 15:46:39 +03:00
# include "MatrixClient.h"
2017-12-30 18:29:57 +03:00
# include "UserSettingsPage.h"
2018-05-05 16:38:41 +03:00
# include "Utils.h"
2021-10-14 23:53:11 +03:00
# include "encryption/DeviceVerificationFlow.h"
# include "encryption/Olm.h"
2022-08-05 22:44:40 +03:00
# include "ui/RoomSummary.h"
2021-04-30 16:33:17 +03:00
# include "ui/UserProfile.h"
2021-10-14 23:53:11 +03:00
# include "voip/CallManager.h"
2017-04-06 02:06:42 +03:00
2018-05-05 22:40:24 +03:00
# include "notifications/Manager.h"
2019-11-09 05:06:10 +03:00
# include "timeline/TimelineViewManager.h"
2017-11-30 14:53:28 +03:00
2023-02-20 01:17:21 +03:00
ChatPage * ChatPage : : instance_ = nullptr ;
static constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000 ;
static constexpr int RETRY_TIMEOUT = 5'000 ;
static constexpr size_t MAX_ONETIME_KEYS = 50 ;
2018-01-03 19:05:49 +03:00
2022-01-12 21:09:46 +03:00
ChatPage : : ChatPage ( QSharedPointer < UserSettings > userSettings , QObject * parent )
: QObject ( parent )
2018-06-09 16:03:14 +03:00
, isConnected_ ( true )
2017-12-30 18:29:57 +03:00
, userSettings_ { userSettings }
2022-06-18 02:35:30 +03:00
, notificationsManager ( new NotificationsManager ( this ) )
2020-10-28 23:08:17 +03:00
, callManager_ ( new CallManager ( this ) )
2017-04-06 02:06:42 +03:00
{
2021-12-29 06:28:08 +03:00
setObjectName ( QStringLiteral ( " chatPage " ) ) ;
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
instance_ = this ;
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
view_manager_ = new TimelineViewManager ( callManager_ , this ) ;
connect ( this ,
& ChatPage : : downloadedSecrets ,
this ,
& ChatPage : : decryptDownloadedSecrets ,
Qt : : QueuedConnection ) ;
connect ( this , & ChatPage : : connectionLost , this , [ this ] ( ) {
nhlog : : net ( ) - > info ( " connectivity lost " ) ;
isConnected_ = false ;
http : : client ( ) - > shutdown ( ) ;
} ) ;
connect ( this , & ChatPage : : connectionRestored , this , [ this ] ( ) {
nhlog : : net ( ) - > info ( " trying to re-connect " ) ;
isConnected_ = true ;
2021-06-11 03:11:49 +03:00
2021-09-18 01:22:33 +03:00
// Drop all pending connections.
http : : client ( ) - > shutdown ( ) ;
trySync ( ) ;
} ) ;
connectivityTimer_ . setInterval ( CHECK_CONNECTIVITY_INTERVAL ) ;
2022-06-13 12:12:41 +03:00
connect ( & connectivityTimer_ , & QTimer : : timeout , this , [ this ] ( ) {
2021-09-18 01:22:33 +03:00
if ( http : : client ( ) - > access_token ( ) . empty ( ) ) {
connectivityTimer_ . stop ( ) ;
return ;
}
http : : client ( ) - > versions (
[ this ] ( const mtx : : responses : : Versions & , mtx : : http : : RequestErr err ) {
if ( err ) {
emit connectionLost ( ) ;
return ;
}
2023-03-14 05:02:54 +03:00
// only update spaces every 20 minutes
if ( lastSpacesUpdate < QDateTime : : currentDateTime ( ) . addSecs ( - 20 * 60 ) ) {
lastSpacesUpdate = QDateTime : : currentDateTime ( ) ;
utils : : updateSpaceVias ( ) ;
2023-07-05 01:08:37 +03:00
utils : : removeExpiredEvents ( ) ;
2023-03-14 05:02:54 +03:00
}
2021-09-18 01:22:33 +03:00
if ( ! isConnected_ )
emit connectionRestored ( ) ;
} ) ;
} ) ;
connect ( this , & ChatPage : : loggedOut , this , & ChatPage : : logout ) ;
connect (
view_manager_ ,
& TimelineViewManager : : inviteUsers ,
this ,
[ this ] ( QString roomId , QStringList users ) {
for ( int ii = 0 ; ii < users . size ( ) ; + + ii ) {
QTimer : : singleShot ( ii * 500 , this , [ this , roomId , ii , users ] ( ) {
const auto user = users . at ( ii ) ;
http : : client ( ) - > invite_user (
roomId . toStdString ( ) ,
user . toStdString ( ) ,
[ this , user ] ( const mtx : : responses : : RoomInvite & , mtx : : http : : RequestErr err ) {
if ( err ) {
emit showNotification ( tr ( " Failed to invite user: %1 " ) . arg ( user ) ) ;
return ;
2019-08-10 06:34:44 +03:00
}
2018-05-05 16:38:41 +03:00
2021-09-18 01:22:33 +03:00
emit showNotification ( tr ( " Invited user: %1 " ) . arg ( user ) ) ;
} ) ;
} ) ;
}
} ) ;
2022-04-01 01:58:01 +03:00
connect ( this ,
& ChatPage : : internalKnock ,
this ,
2022-08-05 22:44:40 +03:00
qOverload < const QString & , const std : : vector < std : : string > & , QString , bool , bool > (
2022-04-01 01:58:01 +03:00
& ChatPage : : knockRoom ) ,
Qt : : QueuedConnection ) ;
2021-09-18 01:22:33 +03:00
connect ( this , & ChatPage : : leftRoom , this , & ChatPage : : removeRoom ) ;
connect ( this , & ChatPage : : changeToRoom , this , & ChatPage : : changeRoom , Qt : : QueuedConnection ) ;
2022-06-18 02:35:30 +03:00
connect ( notificationsManager ,
2021-09-18 01:22:33 +03:00
& NotificationsManager : : notificationClicked ,
this ,
[ this ] ( const QString & roomid , const QString & eventid ) {
Q_UNUSED ( eventid )
2022-05-07 20:03:58 +03:00
auto exWin = MainWindow : : instance ( ) - > windowForRoom ( roomid ) ;
if ( exWin ) {
2023-02-01 20:24:25 +03:00
exWin - > setVisible ( true ) ;
exWin - > raise ( ) ;
2022-05-07 20:03:58 +03:00
exWin - > requestActivate ( ) ;
} else {
view_manager_ - > rooms ( ) - > setCurrentRoom ( roomid ) ;
2023-02-01 20:24:25 +03:00
MainWindow : : instance ( ) - > setVisible ( true ) ;
MainWindow : : instance ( ) - > raise ( ) ;
2022-05-07 20:03:58 +03:00
MainWindow : : instance ( ) - > requestActivate ( ) ;
}
2021-09-18 01:22:33 +03:00
} ) ;
2022-06-18 02:35:30 +03:00
connect ( notificationsManager ,
2021-09-18 01:22:33 +03:00
& NotificationsManager : : sendNotificationReply ,
this ,
2022-11-04 19:42:09 +03:00
& ChatPage : : sendNotificationReply ) ;
2021-09-18 01:22:33 +03:00
connect (
this ,
& ChatPage : : initializeViews ,
view_manager_ ,
2021-11-21 00:48:04 +03:00
[ this ] ( const mtx : : responses : : Sync & sync ) { view_manager_ - > sync ( sync ) ; } ,
2021-09-18 01:22:33 +03:00
Qt : : QueuedConnection ) ;
connect ( this ,
& ChatPage : : initializeEmptyViews ,
view_manager_ ,
& TimelineViewManager : : initializeRoomlist ) ;
2021-11-21 00:48:04 +03:00
connect ( this , & ChatPage : : syncUI , this , [ this ] ( const mtx : : responses : : Sync & sync ) {
view_manager_ - > sync ( sync ) ;
2021-09-18 01:22:33 +03:00
static unsigned int prevNotificationCount = 0 ;
unsigned int notificationCount = 0 ;
2021-11-21 00:48:04 +03:00
for ( const auto & room : sync . rooms . join ) {
2022-10-26 02:10:35 +03:00
notificationCount + =
static_cast < unsigned int > ( room . second . unread_notifications . notification_count ) ;
2021-09-18 01:22:33 +03:00
}
2020-07-18 22:00:36 +03:00
2021-09-18 01:22:33 +03:00
// HACK: If we had less notifications last time we checked, send an alert if the
// user wanted one. Technically, this may cause an alert to be missed if new ones
// come in while you are reading old ones. Since the window is almost certainly open
// in this edge case, that's probably a non-issue.
// TODO: Replace this once we have proper pushrules support. This is a horrible hack
if ( prevNotificationCount < notificationCount ) {
if ( userSettings_ - > hasAlertOnNotification ( ) )
2022-01-12 21:09:46 +03:00
MainWindow : : instance ( ) - > alert ( 0 ) ;
2021-09-18 01:22:33 +03:00
}
prevNotificationCount = notificationCount ;
// No need to check amounts for this section, as this function internally checks for
// duplicates.
if ( notificationCount & & userSettings_ - > hasNotifications ( ) )
2022-10-13 18:19:54 +03:00
for ( const auto & e : sync . account_data . events ) {
if ( auto newRules =
std : : get_if < mtx : : events : : AccountDataEvent < mtx : : pushrules : : GlobalRuleset > > ( & e ) )
pushrules =
std : : make_unique < mtx : : pushrules : : PushRuleEvaluator > ( newRules - > content . global ) ;
}
if ( ! pushrules ) {
auto eventInDb = cache : : client ( ) - > getAccountData ( mtx : : events : : EventType : : PushRules ) ;
if ( eventInDb ) {
if ( auto newRules =
std : : get_if < mtx : : events : : AccountDataEvent < mtx : : pushrules : : GlobalRuleset > > (
& * eventInDb ) )
pushrules =
std : : make_unique < mtx : : pushrules : : PushRuleEvaluator > ( newRules - > content . global ) ;
}
}
if ( pushrules ) {
const auto local_user = utils : : localUser ( ) . toStdString ( ) ;
2023-03-12 12:35:25 +03:00
// Desktop notifications to be sent
std : : vector < std : : tuple < QSharedPointer < TimelineModel > ,
mtx : : events : : collections : : TimelineEvents ,
std : : string ,
std : : vector < mtx : : pushrules : : actions : : Action > > >
notifications ;
2022-10-13 18:19:54 +03:00
for ( const auto & [ room_id , room ] : sync . rooms . join ) {
// clear old notifications
for ( const auto & e : room . ephemeral . events ) {
if ( auto receiptsEv =
std : : get_if < mtx : : events : : EphemeralEvent < mtx : : events : : ephemeral : : Receipt > > (
& e ) ) {
std : : vector < QString > receipts ;
for ( const auto & [ event_id , userReceipts ] : receiptsEv - > content . receipts ) {
if ( auto r = userReceipts . find ( mtx : : events : : ephemeral : : Receipt : : Read ) ;
r ! = userReceipts . end ( ) ) {
for ( const auto & [ user_id , receipt ] : r - > second . users ) {
( void ) receipt ;
if ( user_id = = local_user ) {
receipts . push_back ( QString : : fromStdString ( event_id ) ) ;
break ;
}
}
}
if ( auto r =
userReceipts . find ( mtx : : events : : ephemeral : : Receipt : : ReadPrivate ) ;
r ! = userReceipts . end ( ) ) {
for ( const auto & [ user_id , receipt ] : r - > second . users ) {
( void ) receipt ;
if ( user_id = = local_user ) {
receipts . push_back ( QString : : fromStdString ( event_id ) ) ;
break ;
}
}
}
}
if ( ! receipts . empty ( ) )
notificationsManager - > removeNotifications (
QString : : fromStdString ( room_id ) , receipts ) ;
}
}
// calculate new notifications
if ( ! room . timeline . events . empty ( ) & &
( room . unread_notifications . notification_count | |
room . unread_notifications . highlight_count ) ) {
auto roomModel =
view_manager_ - > rooms ( ) - > getRoomById ( QString : : fromStdString ( room_id ) ) ;
if ( ! roomModel ) {
continue ;
}
auto currentReadMarker =
cache : : getEventIndex ( room_id , cache : : client ( ) - > getFullyReadEventId ( room_id ) ) ;
auto ctx = roomModel - > pushrulesRoomContext ( ) ;
2022-12-03 04:10:45 +03:00
std : : vector <
2023-04-11 01:11:46 +03:00
std : : pair < mtx : : common : : Relation , mtx : : events : : collections : : TimelineEvents > >
2022-12-03 04:10:45 +03:00
relatedEvents ;
2022-10-13 18:19:54 +03:00
for ( const auto & event : room . timeline . events ) {
2022-12-28 23:43:43 +03:00
auto event_id = mtx : : accessors : : event_id ( event ) ;
// skip already read events
if ( currentReadMarker & &
currentReadMarker > cache : : getEventIndex ( room_id , event_id ) )
continue ;
// skip our messages
auto sender = mtx : : accessors : : sender ( event ) ;
if ( sender = = http : : client ( ) - > user_id ( ) . to_string ( ) )
continue ;
2023-04-11 01:11:46 +03:00
mtx : : events : : collections : : TimelineEvents te { event } ;
2023-10-30 16:56:10 +03:00
std : : visit (
[ room_id_ = room_id ] ( auto & event_ ) { event_ . room_id = room_id_ ; } , te ) ;
2022-10-13 18:19:54 +03:00
if ( auto encryptedEvent =
std : : get_if < mtx : : events : : EncryptedEvent < mtx : : events : : msg : : Encrypted > > (
2022-10-13 18:37:28 +03:00
& event ) ;
encryptedEvent & & userSettings_ - > decryptNotifications ( ) ) {
2022-10-13 18:19:54 +03:00
MegolmSessionIndex index ( room_id , encryptedEvent - > content ) ;
auto result = olm : : decryptEvent ( index , * encryptedEvent ) ;
if ( result . event )
2023-04-11 01:11:46 +03:00
te = std : : move ( result . event ) . value ( ) ;
2022-10-13 18:19:54 +03:00
}
2022-12-03 04:10:45 +03:00
relatedEvents . clear ( ) ;
2023-04-11 01:11:46 +03:00
for ( const auto & r : mtx : : accessors : : relations ( te ) . relations ) {
2022-12-03 04:10:45 +03:00
auto related = cache : : client ( ) - > getEvent ( room_id , r . event_id ) ;
if ( related ) {
relatedEvents . emplace_back ( r , * related ) ;
if ( auto encryptedEvent = std : : get_if <
mtx : : events : : EncryptedEvent < mtx : : events : : msg : : Encrypted > > (
2023-04-11 01:11:46 +03:00
& related . value ( ) ) ;
2022-12-03 04:10:45 +03:00
encryptedEvent & & userSettings_ - > decryptNotifications ( ) ) {
MegolmSessionIndex index ( room_id , encryptedEvent - > content ) ;
auto result = olm : : decryptEvent ( index , * encryptedEvent ) ;
if ( result . event )
2023-04-11 01:11:46 +03:00
relatedEvents . back ( ) . second =
std : : move ( result . event ) . value ( ) ;
2022-12-03 04:10:45 +03:00
}
}
}
auto actions = pushrules - > evaluate ( te , ctx , relatedEvents ) ;
2022-10-13 18:19:54 +03:00
if ( std : : find ( actions . begin ( ) ,
actions . end ( ) ,
mtx : : pushrules : : actions : : Action {
mtx : : pushrules : : actions : : notify { } } ) ! = actions . end ( ) ) {
if ( ! cache : : isNotificationSent ( event_id ) ) {
// We should only send one notification per event.
cache : : markSentNotification ( event_id ) ;
// Don't send a notification when the current room is opened.
if ( isRoomActive ( roomModel - > roomId ( ) ) )
continue ;
if ( userSettings_ - > hasDesktopNotifications ( ) ) {
2023-03-12 12:35:25 +03:00
notifications . emplace_back ( roomModel , te , room_id , actions ) ;
2022-10-13 18:19:54 +03:00
}
}
}
}
}
}
2023-03-12 12:35:25 +03:00
if ( notifications . size ( ) < = 5 ) {
for ( const auto & [ roomModel , te , room_id , actions ] : notifications ) {
AvatarProvider : : resolve (
roomModel - > roomAvatarUrl ( ) ,
96 ,
this ,
2023-10-30 16:56:10 +03:00
[ this , te_ = te , room_id_ = room_id , actions_ = actions ] ( QPixmap image ) {
2023-03-12 12:35:25 +03:00
notificationsManager - > postNotification (
mtx : : responses : : Notification {
2023-10-30 16:56:10 +03:00
. actions = actions_ ,
. event = std : : move ( te_ ) ,
2023-03-12 12:35:25 +03:00
. read = false ,
. profile_tag = " " ,
2023-10-30 16:56:10 +03:00
. room_id = room_id_ ,
2023-03-12 12:35:25 +03:00
. ts = 0 ,
} ,
image . toImage ( ) ) ;
} ) ;
}
} else if ( ! notifications . empty ( ) ) {
std : : map < QSharedPointer < TimelineModel > , std : : size_t > missedEvents ;
for ( const auto & [ roomModel , te , room_id , actions ] : notifications ) {
missedEvents [ roomModel ] + + ;
}
QString body ;
for ( const auto & [ roomModel , nbNotifs ] : missedEvents ) {
2023-05-29 21:31:57 +03:00
body + = tr ( " %n unread message(s) in room %1 \n " , nullptr , nbNotifs )
2023-03-12 12:35:25 +03:00
. arg ( roomModel - > roomName ( ) ) ;
}
emit notificationsManager - > systemPostNotificationCb (
" " , " " , " New messages while away " , body , QImage ( ) ) ;
}
2022-10-13 18:19:54 +03:00
}
2021-09-18 01:22:33 +03:00
} ) ;
connect (
this , & ChatPage : : tryInitialSyncCb , this , & ChatPage : : tryInitialSync , Qt : : QueuedConnection ) ;
connect ( this , & ChatPage : : trySyncCb , this , & ChatPage : : trySync , Qt : : QueuedConnection ) ;
connect (
this ,
& ChatPage : : tryDelayedSyncCb ,
this ,
[ this ] ( ) { QTimer : : singleShot ( RETRY_TIMEOUT , this , & ChatPage : : trySync ) ; } ,
Qt : : QueuedConnection ) ;
connect (
this , & ChatPage : : newSyncResponse , this , & ChatPage : : handleSyncResponse , Qt : : QueuedConnection ) ;
connect ( this , & ChatPage : : dropToLoginPageCb , this , & ChatPage : : dropToLoginPage ) ;
2022-05-14 02:42:21 +03:00
connect (
this ,
& ChatPage : : startRemoveFallbackKeyTimer ,
this ,
[ this ] ( ) {
QTimer : : singleShot ( std : : chrono : : minutes ( 5 ) , this , & ChatPage : : removeOldFallbackKey ) ;
2022-05-14 03:22:33 +03:00
disconnect (
this , & ChatPage : : newSyncResponse , this , & ChatPage : : startRemoveFallbackKeyTimer ) ;
2022-05-14 02:42:21 +03:00
} ,
Qt : : QueuedConnection ) ;
2023-03-14 05:02:54 +03:00
connect (
this ,
& ChatPage : : callFunctionOnGuiThread ,
this ,
[ ] ( std : : function < void ( ) > f ) { f ( ) ; } ,
Qt : : QueuedConnection ) ;
2023-10-26 00:22:39 +03:00
connect ( qobject_cast < QGuiApplication * > ( QGuiApplication : : instance ( ) ) ,
& QGuiApplication : : focusWindowChanged ,
this ,
[ this ] ( QWindow * activeWindow ) {
if ( activeWindow ) {
nhlog : : ui ( ) - > debug ( " Stopping inactive timer. " ) ;
lastWindowActive = QDateTime ( ) ;
} else {
nhlog : : ui ( ) - > debug ( " Starting inactive timer. " ) ;
lastWindowActive = QDateTime : : currentDateTime ( ) ;
}
} ) ;
2022-06-27 19:09:31 +03:00
connectCallMessage < mtx : : events : : voip : : CallInvite > ( ) ;
connectCallMessage < mtx : : events : : voip : : CallCandidates > ( ) ;
connectCallMessage < mtx : : events : : voip : : CallAnswer > ( ) ;
connectCallMessage < mtx : : events : : voip : : CallHangUp > ( ) ;
2022-10-14 16:49:05 +03:00
connectCallMessage < mtx : : events : : voip : : CallSelectAnswer > ( ) ;
connectCallMessage < mtx : : events : : voip : : CallReject > ( ) ;
connectCallMessage < mtx : : events : : voip : : CallNegotiate > ( ) ;
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
ChatPage : : logout ( )
2017-04-09 02:17:04 +03:00
{
2021-09-18 01:22:33 +03:00
resetUI ( ) ;
deleteConfigs ( ) ;
2017-10-20 22:32:48 +03:00
2021-09-18 01:22:33 +03:00
emit closing ( ) ;
connectivityTimer_ . stop ( ) ;
2018-06-09 16:03:14 +03:00
}
void
ChatPage : : dropToLoginPage ( const QString & msg )
{
2021-09-18 01:22:33 +03:00
nhlog : : ui ( ) - > info ( " dropping to the login page: {} " , msg . toStdString ( ) ) ;
2018-08-30 13:39:09 +03:00
2021-09-18 01:22:33 +03:00
http : : client ( ) - > shutdown ( ) ;
connectivityTimer_ . stop ( ) ;
2018-06-09 16:03:14 +03:00
2022-11-06 03:06:01 +03:00
auto btn = QMessageBox : : warning (
nullptr ,
tr ( " Confirm logout " ) ,
tr ( " Because of the following reason Nheko wants to drop you to the login page: \n %1 \n If you "
2022-12-28 22:49:42 +03:00
" think this is a mistake, you can close Nheko instead to possibly recover your encryption "
2022-11-06 03:06:01 +03:00
" keys. After you have been dropped to the login page, you can sign in again using your "
2022-11-06 05:36:56 +03:00
" usual methods. " )
. arg ( msg ) ,
2022-11-06 03:06:01 +03:00
QMessageBox : : StandardButton : : Close | QMessageBox : : StandardButton : : Ok ,
QMessageBox : : StandardButton : : Ok ) ;
if ( btn = = QMessageBox : : StandardButton : : Close ) {
QCoreApplication : : exit ( 1 ) ;
exit ( 1 ) ;
}
2021-09-18 01:22:33 +03:00
resetUI ( ) ;
deleteConfigs ( ) ;
2020-12-17 00:10:09 +03:00
2021-09-18 01:22:33 +03:00
emit showLoginPage ( msg ) ;
2017-10-20 22:32:48 +03:00
}
void
ChatPage : : resetUI ( )
{
2021-09-18 01:22:33 +03:00
view_manager_ - > clearAll ( ) ;
2017-10-21 16:46:11 +03:00
2021-09-18 01:22:33 +03:00
emit unreadMessages ( 0 ) ;
2017-10-20 22:32:48 +03:00
}
void
ChatPage : : deleteConfigs ( )
{
2021-09-18 01:22:33 +03:00
auto settings = UserSettings : : instance ( ) - > qsettings ( ) ;
2021-12-29 06:28:08 +03:00
if ( UserSettings : : instance ( ) - > profile ( ) ! = QLatin1String ( " " ) ) {
settings - > beginGroup ( QStringLiteral ( " profile " ) ) ;
2021-09-18 01:22:33 +03:00
settings - > beginGroup ( UserSettings : : instance ( ) - > profile ( ) ) ;
}
2021-12-29 06:28:08 +03:00
settings - > beginGroup ( QStringLiteral ( " auth " ) ) ;
settings - > remove ( QLatin1String ( " " ) ) ;
2021-09-18 01:22:33 +03:00
settings - > endGroup ( ) ; // auth
2022-06-15 23:36:16 +03:00
if ( UserSettings : : instance ( ) - > profile ( ) ! = QLatin1String ( " " ) ) {
settings - > endGroup ( ) ; // profilename
settings - > endGroup ( ) ; // profile
}
2021-09-18 01:22:33 +03:00
http : : client ( ) - > shutdown ( ) ;
cache : : deleteData ( ) ;
2017-04-09 02:17:04 +03:00
}
2017-08-20 13:47:22 +03:00
void
ChatPage : : bootstrap ( QString userid , QString homeserver , QString token )
2017-04-06 02:06:42 +03:00
{
2021-09-18 01:22:33 +03:00
using namespace mtx : : identifiers ;
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
try {
http : : client ( ) - > set_user ( parse < User > ( userid . toStdString ( ) ) ) ;
} catch ( const std : : invalid_argument & ) {
nhlog : : ui ( ) - > critical ( " bootstrapped with invalid user_id: {} " , userid . toStdString ( ) ) ;
}
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
http : : client ( ) - > set_server ( homeserver . toStdString ( ) ) ;
http : : client ( ) - > set_access_token ( token . toStdString ( ) ) ;
http : : client ( ) - > verify_certificates ( ! UserSettings : : instance ( ) - > disableCertificateValidation ( ) ) ;
2017-08-26 13:49:16 +03:00
2021-09-18 01:22:33 +03:00
// The Olm client needs the user_id & device_id that will be included
// in the generated payloads & keys.
olm : : client ( ) - > set_user_id ( http : : client ( ) - > user_id ( ) . to_string ( ) ) ;
olm : : client ( ) - > set_device_id ( http : : client ( ) - > device_id ( ) ) ;
2018-06-10 20:03:45 +03:00
2021-09-18 01:22:33 +03:00
try {
cache : : init ( userid ) ;
2021-11-07 05:38:48 +03:00
connect ( cache : : client ( ) , & Cache : : databaseReady , this , [ this ] ( ) {
nhlog : : db ( ) - > info ( " database ready " ) ;
const bool isInitialized = cache : : isInitialized ( ) ;
const auto cacheVersion = cache : : formatVersion ( ) ;
try {
if ( ! isInitialized ) {
cache : : setCurrentFormat ( ) ;
} else {
if ( cacheVersion = = cache : : CacheVersion : : Current ) {
loadStateFromCache ( ) ;
return ;
} else if ( cacheVersion = = cache : : CacheVersion : : Older ) {
if ( ! cache : : runMigrations ( ) ) {
QMessageBox : : critical (
2022-01-12 21:09:46 +03:00
nullptr ,
2021-11-07 05:38:48 +03:00
tr ( " Cache migration failed! " ) ,
tr ( " Migrating the cache to the current version failed. "
" This can have different reasons. Please open an "
2022-09-30 07:04:50 +03:00
" issue at https://github.com/Nheko-Reborn/nheko and try to use an "
" older version in the meantime. Alternatively you can try "
" deleting the cache manually. " ) ) ;
2021-11-07 05:38:48 +03:00
QCoreApplication : : quit ( ) ;
}
loadStateFromCache ( ) ;
return ;
} else if ( cacheVersion = = cache : : CacheVersion : : Newer ) {
QMessageBox : : critical (
2022-01-12 21:09:46 +03:00
nullptr ,
2021-11-07 05:38:48 +03:00
tr ( " Incompatible cache version " ) ,
tr ( " The cache on your disk is newer than this version of Nheko "
" supports. Please update Nheko or clear your cache. " ) ) ;
QCoreApplication : : quit ( ) ;
return ;
}
}
// It's the first time syncing with this device
// There isn't a saved olm account to restore.
nhlog : : crypto ( ) - > info ( " creating new olm account " ) ;
olm : : client ( ) - > create_new_account ( ) ;
cache : : saveOlmAccount ( olm : : client ( ) - > save ( cache : : client ( ) - > pickleSecret ( ) ) ) ;
} catch ( const lmdb : : error & e ) {
nhlog : : crypto ( ) - > critical ( " failed to save olm account {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( QString : : fromStdString ( e . what ( ) ) ) ;
return ;
} catch ( const mtx : : crypto : : olm_exception & e ) {
nhlog : : crypto ( ) - > critical ( " failed to create new olm account {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( QString : : fromStdString ( e . what ( ) ) ) ;
return ;
}
getProfileInfo ( ) ;
getBackupVersion ( ) ;
tryInitialSync ( ) ;
callManager_ - > refreshTurnServer ( ) ;
2021-11-07 23:27:58 +03:00
emit MainWindow : : instance ( ) - > reload ( ) ;
2021-11-07 05:38:48 +03:00
} ) ;
2021-09-18 01:22:33 +03:00
connect ( cache : : client ( ) ,
& Cache : : newReadReceipts ,
view_manager_ ,
& TimelineViewManager : : updateReadReceipts ) ;
2023-02-18 04:59:33 +03:00
connect ( cache : : client ( ) , & Cache : : secretChanged , this , [ this ] ( const std : : string & secret ) {
if ( secret = = mtx : : secret_storage : : secrets : : megolm_backup_v1 ) {
getBackupVersion ( ) ;
}
} ) ;
2021-09-18 01:22:33 +03:00
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > critical ( " failure during boot: {} " , e . what ( ) ) ;
2021-11-07 05:38:48 +03:00
emit dropToLoginPageCb ( tr ( " Failed to open database, logging out! " ) ) ;
2021-09-18 01:22:33 +03:00
}
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
ChatPage : : loadStateFromCache ( )
2017-07-29 11:49:00 +03:00
{
2021-09-18 01:22:33 +03:00
nhlog : : db ( ) - > info ( " restoring state from cache " ) ;
try {
olm : : client ( ) - > load ( cache : : restoreOlmAccount ( ) , cache : : client ( ) - > pickleSecret ( ) ) ;
2023-10-11 22:07:35 +03:00
nhlog : : db ( ) - > info ( " Removing old cached messages " ) ;
cache : : deleteOldData ( ) ;
nhlog : : db ( ) - > info ( " Message removal done " ) ;
2021-09-18 01:22:33 +03:00
emit initializeEmptyViews ( ) ;
cache : : calculateRoomReadStatus ( ) ;
} catch ( const mtx : : crypto : : olm_exception & e ) {
nhlog : : crypto ( ) - > critical ( " failed to restore olm account: {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( tr ( " Failed to restore OLM account. Please login again. " ) ) ;
return ;
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > critical ( " failed to restore cache: {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( tr ( " Failed to restore save data. Please login again. " ) ) ;
return ;
2022-06-17 23:03:54 +03:00
} catch ( const nlohmann : : json : : exception & e ) {
2021-09-18 01:22:33 +03:00
nhlog : : db ( ) - > critical ( " failed to parse cache data: {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( tr ( " Failed to restore save data. Please login again. " ) ) ;
return ;
} catch ( const std : : exception & e ) {
nhlog : : db ( ) - > critical ( " failed to load cache data: {} " , e . what ( ) ) ;
emit dropToLoginPageCb ( tr ( " Failed to restore save data. Please login again. " ) ) ;
return ;
}
nhlog : : crypto ( ) - > info ( " ed25519 : {} " , olm : : client ( ) - > identity_keys ( ) . ed25519 ) ;
nhlog : : crypto ( ) - > info ( " curve25519: {} " , olm : : client ( ) - > identity_keys ( ) . curve25519 ) ;
getProfileInfo ( ) ;
getBackupVersion ( ) ;
verifyOneTimeKeyCountAfterStartup ( ) ;
2021-12-13 20:32:54 +03:00
callManager_ - > refreshTurnServer ( ) ;
2021-09-18 01:22:33 +03:00
emit contentLoaded ( ) ;
// Start receiving events.
2022-05-14 02:42:21 +03:00
connect ( this , & ChatPage : : newSyncResponse , & ChatPage : : startRemoveFallbackKeyTimer ) ;
2021-09-18 01:22:33 +03:00
emit trySyncCb ( ) ;
2017-07-29 11:49:00 +03:00
}
2017-12-19 23:36:12 +03:00
void
2018-04-21 16:34:50 +03:00
ChatPage : : removeRoom ( const QString & room_id )
2017-12-19 23:36:12 +03:00
{
2021-09-18 01:22:33 +03:00
try {
cache : : removeRoom ( room_id ) ;
cache : : removeInvite ( room_id . toStdString ( ) ) ;
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > critical ( " failure while removing room: {} " , e . what ( ) ) ;
// TODO: Notify the user.
}
2018-02-15 22:58:57 +03:00
}
2018-05-05 16:38:41 +03:00
2018-06-09 16:03:14 +03:00
void
ChatPage : : tryInitialSync ( )
{
2021-09-18 01:22:33 +03:00
nhlog : : crypto ( ) - > info ( " ed25519 : {} " , olm : : client ( ) - > identity_keys ( ) . ed25519 ) ;
nhlog : : crypto ( ) - > info ( " curve25519: {} " , olm : : client ( ) - > identity_keys ( ) . curve25519 ) ;
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
// Upload one time keys for the device.
nhlog : : crypto ( ) - > info ( " generating one time keys " ) ;
2022-05-14 02:42:21 +03:00
olm : : client ( ) - > generate_one_time_keys ( MAX_ONETIME_KEYS , true ) ;
2018-06-09 16:03:14 +03:00
2021-09-18 01:22:33 +03:00
http : : client ( ) - > upload_keys (
olm : : client ( ) - > create_upload_keys_request ( ) ,
[ this ] ( const mtx : : responses : : UploadKeys & res , mtx : : http : : RequestErr err ) {
if ( err ) {
const int status_code = static_cast < int > ( err - > status_code ) ;
2018-09-05 16:57:26 +03:00
2021-09-18 01:22:33 +03:00
if ( status_code = = 404 ) {
nhlog : : net ( ) - > warn ( " skipping key uploading. server doesn't provide /keys/upload " ) ;
return startInitialSync ( ) ;
}
2018-08-30 20:37:24 +03:00
2021-11-29 08:20:43 +03:00
nhlog : : crypto ( ) - > critical ( " failed to upload one time keys: {} " , err ) ;
2018-09-05 16:57:26 +03:00
2021-09-18 01:22:33 +03:00
QString errorMsg ( tr ( " Failed to setup encryption keys. Server response: "
" %1 %2. Please try again later. " )
. arg ( QString : : fromStdString ( err - > matrix_error . error ) )
. arg ( status_code ) ) ;
2018-09-05 16:57:26 +03:00
2021-09-18 01:22:33 +03:00
emit dropToLoginPageCb ( errorMsg ) ;
return ;
}
2018-06-09 16:03:14 +03:00
2022-05-14 02:42:21 +03:00
olm : : client ( ) - > forget_old_fallback_key ( ) ;
2021-09-18 01:22:33 +03:00
olm : : mark_keys_as_published ( ) ;
2018-06-18 12:56:47 +03:00
2021-09-18 01:22:33 +03:00
for ( const auto & entry : res . one_time_key_counts )
nhlog : : net ( ) - > info ( " uploaded {} {} one-time keys " , entry . second , entry . first ) ;
2018-06-10 20:03:45 +03:00
2021-11-08 18:26:16 +03:00
cache : : client ( ) - > markUserKeysOutOfDate ( { http : : client ( ) - > user_id ( ) . to_string ( ) } ) ;
2021-09-18 01:22:33 +03:00
startInitialSync ( ) ;
} ) ;
2018-06-09 16:03:14 +03:00
}
2018-08-30 13:39:09 +03:00
void
ChatPage : : startInitialSync ( )
{
2021-09-18 01:22:33 +03:00
nhlog : : net ( ) - > info ( " trying initial sync " ) ;
mtx : : http : : SyncOpts opts ;
opts . timeout = 0 ;
opts . set_presence = currentPresence ( ) ;
http : : client ( ) - > sync ( opts , [ this ] ( const mtx : : responses : : Sync & res , mtx : : http : : RequestErr err ) {
// TODO: Initial Sync should include mentions as well...
if ( err ) {
const auto error = QString : : fromStdString ( err - > matrix_error . error ) ;
const auto msg = tr ( " Please try to login again: %1 " ) . arg ( error ) ;
const auto err_code = mtx : : errors : : to_string ( err - > matrix_error . errcode ) ;
const int status_code = static_cast < int > ( err - > status_code ) ;
2021-11-29 08:20:43 +03:00
nhlog : : net ( ) - > error ( " initial sync error: {} " , err ) ;
2021-09-18 01:22:33 +03:00
// non http related errors
if ( status_code < = 0 | | status_code > = 600 ) {
startInitialSync ( ) ;
return ;
}
2020-10-27 19:45:28 +03:00
2021-09-18 01:22:33 +03:00
switch ( status_code ) {
case 502 :
case 504 :
case 524 : {
startInitialSync ( ) ;
return ;
}
default : {
emit dropToLoginPageCb ( msg ) ;
return ;
}
}
}
2020-10-27 19:45:28 +03:00
2022-04-08 04:24:10 +03:00
QTimer : : singleShot ( 0 , this , [ this , res ] {
nhlog : : net ( ) - > info ( " initial sync completed " ) ;
try {
cache : : client ( ) - > saveState ( res ) ;
2020-10-27 19:45:28 +03:00
2022-04-08 04:24:10 +03:00
olm : : handle_to_device_messages ( res . to_device . events ) ;
2020-10-27 19:45:28 +03:00
2022-04-08 04:24:10 +03:00
emit initializeViews ( std : : move ( res ) ) ;
2020-10-27 19:45:28 +03:00
2022-04-08 04:24:10 +03:00
cache : : calculateRoomReadStatus ( ) ;
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > error ( " failed to save state after initial sync: {} " , e . what ( ) ) ;
startInitialSync ( ) ;
return ;
}
2020-10-27 19:45:28 +03:00
2022-04-08 04:24:10 +03:00
emit trySyncCb ( ) ;
emit contentLoaded ( ) ;
} ) ;
2021-09-18 01:22:33 +03:00
} ) ;
2018-08-30 13:39:09 +03:00
}
2020-07-18 22:00:36 +03:00
void
2021-03-16 23:01:14 +03:00
ChatPage : : handleSyncResponse ( const mtx : : responses : : Sync & res , const std : : string & prev_batch_token )
2020-07-18 22:00:36 +03:00
{
2021-09-18 01:22:33 +03:00
try {
if ( prev_batch_token ! = cache : : nextBatchToken ( ) ) {
nhlog : : net ( ) - > warn ( " Duplicate sync, dropping " ) ;
return ;
2021-03-16 23:01:14 +03:00
}
2022-10-02 13:31:03 +03:00
} catch ( const lmdb : : error & ) {
2021-09-18 01:22:33 +03:00
nhlog : : db ( ) - > warn ( " Logged out in the mean time, dropping sync " ) ;
2022-06-12 20:39:36 +03:00
return ;
2021-09-18 01:22:33 +03:00
}
2021-03-16 23:01:14 +03:00
2021-09-18 01:22:33 +03:00
nhlog : : net ( ) - > debug ( " sync completed: {} " , res . next_batch ) ;
2020-07-18 22:00:36 +03:00
2021-09-18 01:22:33 +03:00
// Ensure that we have enough one-time keys available.
2023-10-31 18:38:15 +03:00
std : : map < std : : string_view , std : : uint16_t > counts { res . device_one_time_keys_count . begin ( ) ,
res . device_one_time_keys_count . end ( ) } ;
ensureOneTimeKeyCount ( counts , res . device_unused_fallback_key_types ) ;
2020-07-18 22:00:36 +03:00
2023-10-24 02:12:01 +03:00
std : : optional < mtx : : events : : account_data : : IgnoredUsers > oldIgnoredUsers ;
if ( auto ignoreEv = std : : ranges : : find_if (
res . account_data . events ,
[ ] ( const mtx : : events : : collections : : RoomAccountDataEvents & e ) {
return std : : holds_alternative <
mtx : : events : : AccountDataEvent < mtx : : events : : account_data : : IgnoredUsers > > ( e ) ;
} ) ;
ignoreEv ! = res . account_data . events . end ( ) ) {
if ( auto oldEv = cache : : client ( ) - > getAccountData ( mtx : : events : : EventType : : IgnoredUsers ) )
oldIgnoredUsers =
std : : get < mtx : : events : : AccountDataEvent < mtx : : events : : account_data : : IgnoredUsers > > (
* oldEv )
. content ;
else
oldIgnoredUsers = mtx : : events : : account_data : : IgnoredUsers { } ;
}
2021-09-18 01:22:33 +03:00
// TODO: fine grained error handling
try {
cache : : client ( ) - > saveState ( res ) ;
olm : : handle_to_device_messages ( res . to_device . events ) ;
2020-07-18 22:00:36 +03:00
2021-09-18 01:22:33 +03:00
auto updates = cache : : getRoomInfo ( cache : : client ( ) - > roomsWithStateUpdates ( res ) ) ;
2020-07-18 22:00:36 +03:00
2021-11-21 00:48:04 +03:00
emit syncUI ( std : : move ( res ) ) ;
2023-10-24 02:12:01 +03:00
// if the ignored users changed, clear timeline of all affected rooms.
if ( oldIgnoredUsers ) {
if ( auto newEv =
cache : : client ( ) - > getAccountData ( mtx : : events : : EventType : : IgnoredUsers ) ) {
std : : vector < mtx : : events : : account_data : : IgnoredUser > changedUsers { } ;
std : : ranges : : set_symmetric_difference (
oldIgnoredUsers - > users ,
std : : get < mtx : : events : : AccountDataEvent < mtx : : events : : account_data : : IgnoredUsers > > (
* newEv )
. content . users ,
std : : back_inserter ( changedUsers ) ,
{ } ,
& mtx : : events : : account_data : : IgnoredUser : : id ,
& mtx : : events : : account_data : : IgnoredUser : : id ) ;
std : : unordered_set < std : : string > roomsToReload ;
for ( const auto & user : changedUsers ) {
auto commonRooms = cache : : client ( ) - > getCommonRooms ( user . id ) ;
for ( const auto & room : commonRooms )
roomsToReload . insert ( room . first ) ;
}
for ( const auto & room : roomsToReload ) {
if ( auto model =
view_manager_ - > rooms ( ) - > getRoomById ( QString : : fromStdString ( room ) ) )
model - > clearTimeline ( ) ;
}
}
}
2021-09-18 01:22:33 +03:00
} catch ( const lmdb : : map_full_error & e ) {
nhlog : : db ( ) - > error ( " lmdb is full: {} " , e . what ( ) ) ;
cache : : deleteOldData ( ) ;
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > error ( " saving sync response: {} " , e . what ( ) ) ;
}
emit trySyncCb ( ) ;
2020-07-18 22:00:36 +03:00
}
2018-06-09 16:03:14 +03:00
void
ChatPage : : trySync ( )
{
2021-09-18 01:22:33 +03:00
mtx : : http : : SyncOpts opts ;
opts . set_presence = currentPresence ( ) ;
if ( ! connectivityTimer_ . isActive ( ) )
connectivityTimer_ . start ( ) ;
try {
opts . since = cache : : nextBatchToken ( ) ;
} catch ( const lmdb : : error & e ) {
nhlog : : db ( ) - > error ( " failed to retrieve next batch token: {} " , e . what ( ) ) ;
return ;
}
http : : client ( ) - > sync (
opts , [ this , since = opts . since ] ( const mtx : : responses : : Sync & res , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-11-21 07:23:38 +03:00
const auto error = QString : : fromStdString ( err - > matrix_error . error ) ;
const auto msg = tr ( " Please try to login again: %1 " ) . arg ( error ) ;
2021-09-18 01:22:33 +03:00
if ( ( http : : is_logged_in ( ) & &
( err - > matrix_error . errcode = = mtx : : errors : : ErrorCode : : M_UNKNOWN_TOKEN | |
err - > matrix_error . errcode = = mtx : : errors : : ErrorCode : : M_MISSING_TOKEN ) ) | |
! http : : is_logged_in ( ) ) {
emit dropToLoginPageCb ( msg ) ;
return ;
}
2021-11-21 07:04:48 +03:00
nhlog : : net ( ) - > error ( " sync error: {} " , * err ) ;
2021-09-18 01:22:33 +03:00
emit tryDelayedSyncCb ( ) ;
return ;
}
emit newSyncResponse ( res , since ) ;
} ) ;
2018-06-09 16:03:14 +03:00
}
2022-03-30 22:15:22 +03:00
void
2022-04-01 01:58:01 +03:00
ChatPage : : knockRoom ( const QString & room ,
const std : : vector < std : : string > & via ,
QString reason ,
2022-08-05 22:44:40 +03:00
bool failedJoin ,
bool promptForConfirmation )
2022-03-30 22:15:22 +03:00
{
const auto room_id = room . toStdString ( ) ;
2022-04-01 01:58:01 +03:00
bool confirmed = false ;
2022-08-05 22:44:40 +03:00
if ( promptForConfirmation ) {
reason = QInputDialog : : getText (
nullptr ,
tr ( " Knock on room " ) ,
// clang-format off
2022-04-01 01:58:01 +03:00
failedJoin
2022-09-30 07:23:39 +03:00
? tr ( " You failed to join %1. You can try to knock so that others can invite you in. Do you want to do so? \n You may optionally provide a reason for others to accept your knock: " ) . arg ( room )
2022-04-01 02:09:45 +03:00
: tr ( " Do you really want to knock on %1? You may optionally provide a reason for others to accept your knock: " ) . arg ( room ) ,
2022-08-05 22:44:40 +03:00
// clang-format on
QLineEdit : : Normal ,
reason ,
& confirmed ) ;
if ( ! confirmed ) {
return ;
}
2022-04-01 01:58:01 +03:00
}
2022-03-30 22:15:22 +03:00
http : : client ( ) - > knock_room (
2022-03-31 00:38:38 +03:00
room_id ,
2022-04-01 01:58:01 +03:00
via ,
2022-03-31 00:38:38 +03:00
[ this , room_id ] ( const mtx : : responses : : RoomId & , mtx : : http : : RequestErr err ) {
2022-03-30 22:15:22 +03:00
if ( err ) {
emit showNotification ( tr ( " Failed to knock room: %1 " )
. arg ( QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
return ;
}
2022-03-31 00:38:38 +03:00
} ,
reason . toStdString ( ) ) ;
2022-03-30 22:15:22 +03:00
}
2018-06-09 16:03:14 +03:00
void
2022-03-31 00:38:38 +03:00
ChatPage : : joinRoom ( const QString & room , const QString & reason )
2018-06-09 16:03:14 +03:00
{
2021-09-18 01:22:33 +03:00
const auto room_id = room . toStdString ( ) ;
2022-03-31 00:38:38 +03:00
joinRoomVia ( room_id , { } , false , reason ) ;
2021-01-11 21:15:43 +03:00
}
2018-06-09 16:03:14 +03:00
2021-01-11 21:15:43 +03:00
void
2021-03-05 16:59:59 +03:00
ChatPage : : joinRoomVia ( const std : : string & room_id ,
const std : : vector < std : : string > & via ,
2022-03-31 00:38:38 +03:00
bool promptForConfirmation ,
const QString & reason )
2021-01-11 21:15:43 +03:00
{
2022-08-05 22:44:40 +03:00
if ( promptForConfirmation ) {
auto prompt = new RoomSummary ( room_id , via , reason ) ;
QQmlEngine : : setObjectOwnership ( prompt , QQmlEngine : : JavaScriptOwnership ) ;
emit showRoomJoinPrompt ( prompt ) ;
2021-09-18 01:22:33 +03:00
return ;
2022-08-05 22:44:40 +03:00
}
2021-09-18 01:22:33 +03:00
http : : client ( ) - > join_room (
2022-03-31 00:38:38 +03:00
room_id ,
via ,
2022-04-01 01:58:01 +03:00
[ this , room_id , reason , via ] ( const mtx : : responses : : RoomId & , mtx : : http : : RequestErr err ) {
2021-09-18 01:22:33 +03:00
if ( err ) {
2022-04-01 01:58:01 +03:00
if ( err - > matrix_error . errcode = = mtx : : errors : : ErrorCode : : M_FORBIDDEN )
2022-08-05 22:44:40 +03:00
emit internalKnock ( QString : : fromStdString ( room_id ) , via , reason , true , true ) ;
2022-04-01 01:58:01 +03:00
else
emit showNotification ( tr ( " Failed to join room: %1 " )
. arg ( QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-09-18 01:22:33 +03:00
return ;
}
// We remove any invites with the same room_id.
try {
cache : : removeInvite ( room_id ) ;
} catch ( const lmdb : : error & e ) {
emit showNotification ( tr ( " Failed to remove invite: %1 " ) . arg ( e . what ( ) ) ) ;
}
view_manager_ - > rooms ( ) - > setCurrentRoom ( QString : : fromStdString ( room_id ) ) ;
2022-03-31 00:38:38 +03:00
} ,
reason . toStdString ( ) ) ;
2018-06-09 16:03:14 +03:00
}
void
ChatPage : : createRoom ( const mtx : : requests : : CreateRoom & req )
{
2022-04-01 23:04:54 +03:00
if ( req . room_alias_name . find ( " : " ) ! = std : : string : : npos | |
req . room_alias_name . find ( " # " ) ! = std : : string : : npos ) {
nhlog : : net ( ) - > warn ( " Failed to create room: Some characters are not allowed in alias " ) ;
emit this - > showNotification ( tr ( " Room creation failed: Bad Alias " ) ) ;
return ;
}
2021-09-18 01:22:33 +03:00
http : : client ( ) - > create_room (
req , [ this ] ( const mtx : : responses : : CreateRoom & res , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-11-29 08:20:43 +03:00
const auto err_code = mtx : : errors : : to_string ( err - > matrix_error . errcode ) ;
const auto error = err - > matrix_error . error ;
2021-09-18 01:22:33 +03:00
2021-11-29 08:20:43 +03:00
nhlog : : net ( ) - > warn ( " failed to create room: {}) " , err ) ;
2021-09-18 01:22:33 +03:00
emit showNotification (
tr ( " Room creation failed: %1 " ) . arg ( QString : : fromStdString ( error ) ) ) ;
return ;
}
QString newRoomId = QString : : fromStdString ( res . room_id . to_string ( ) ) ;
emit showNotification ( tr ( " Room %1 created. " ) . arg ( newRoomId ) ) ;
emit newRoom ( newRoomId ) ;
emit changeToRoom ( newRoomId ) ;
} ) ;
2018-06-09 16:03:14 +03:00
}
void
2022-03-31 00:38:38 +03:00
ChatPage : : leaveRoom ( const QString & room_id , const QString & reason )
2018-06-09 16:03:14 +03:00
{
2021-09-18 01:22:33 +03:00
http : : client ( ) - > leave_room (
room_id . toStdString ( ) ,
[ this , room_id ] ( const mtx : : responses : : Empty & , mtx : : http : : RequestErr err ) {
if ( err ) {
emit showNotification ( tr ( " Failed to leave room: %1 " )
. arg ( QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-11-29 08:06:51 +03:00
nhlog : : net ( ) - > error ( " Failed to leave room '{}': {} " , room_id . toStdString ( ) , err ) ;
if ( err - > status_code = = 404 & &
err - > matrix_error . errcode = = mtx : : errors : : ErrorCode : : M_UNKNOWN ) {
nhlog : : db ( ) - > debug (
" Removing invite and room for {}, even though we couldn't leave. " ,
room_id . toStdString ( ) ) ;
cache : : client ( ) - > removeInvite ( room_id . toStdString ( ) ) ;
cache : : client ( ) - > removeRoom ( room_id . toStdString ( ) ) ;
}
2021-09-18 01:22:33 +03:00
return ;
}
emit leftRoom ( room_id ) ;
2022-03-31 00:38:38 +03:00
} ,
reason . toStdString ( ) ) ;
2018-06-09 16:03:14 +03:00
}
2021-02-25 07:59:30 +03:00
void
ChatPage : : changeRoom ( const QString & room_id )
{
2021-09-18 01:22:33 +03:00
view_manager_ - > rooms ( ) - > setCurrentRoom ( room_id ) ;
2021-02-25 07:59:30 +03:00
}
2020-01-30 05:45:27 +03:00
void
2022-05-07 19:53:16 +03:00
ChatPage : : inviteUser ( const QString & room , QString userid , QString reason )
2020-01-30 05:45:27 +03:00
{
2022-01-12 21:09:46 +03:00
if ( QMessageBox : : question ( nullptr ,
2021-09-18 01:22:33 +03:00
tr ( " Confirm invite " ) ,
tr ( " Do you really want to invite %1 (%2)? " )
2021-12-29 00:30:12 +03:00
. arg ( cache : : displayName ( room , userid ) , userid ) ) ! = QMessageBox : : Yes )
2021-09-18 01:22:33 +03:00
return ;
http : : client ( ) - > invite_user (
room . toStdString ( ) ,
userid . toStdString ( ) ,
[ this , userid , room ] ( const mtx : : responses : : Empty & , mtx : : http : : RequestErr err ) {
if ( err ) {
2022-01-25 13:09:54 +03:00
nhlog : : net ( ) - > error (
" Failed to invite {} to {}: {} " , userid . toStdString ( ) , room . toStdString ( ) , * err ) ;
2021-12-29 00:30:12 +03:00
emit showNotification (
tr ( " Failed to invite %1 to %2: %3 " )
. arg ( userid , room , QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-09-18 01:22:33 +03:00
} else
emit showNotification ( tr ( " Invited user: %1 " ) . arg ( userid ) ) ;
} ,
reason . trimmed ( ) . toStdString ( ) ) ;
2020-01-30 05:45:27 +03:00
}
void
2022-05-07 19:53:16 +03:00
ChatPage : : kickUser ( const QString & room , QString userid , QString reason )
2020-01-30 05:45:27 +03:00
{
2022-03-06 17:59:32 +03:00
bool confirmed ;
reason =
QInputDialog : : getText ( nullptr ,
tr ( " Reason for the kick " ) ,
tr ( " Enter reason for kicking %1 (%2) or hit enter for no reason: " )
. arg ( cache : : displayName ( room , userid ) , userid ) ,
QLineEdit : : Normal ,
reason ,
& confirmed ) ;
if ( ! confirmed ) {
2021-09-18 01:22:33 +03:00
return ;
2022-03-06 17:59:32 +03:00
}
2021-09-18 01:22:33 +03:00
http : : client ( ) - > kick_user (
room . toStdString ( ) ,
userid . toStdString ( ) ,
[ this , userid , room ] ( const mtx : : responses : : Empty & , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-12-29 00:30:12 +03:00
emit showNotification (
tr ( " Failed to kick %1 from %2: %3 " )
. arg ( userid , room , QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-09-18 01:22:33 +03:00
} else
emit showNotification ( tr ( " Kicked user: %1 " ) . arg ( userid ) ) ;
} ,
reason . trimmed ( ) . toStdString ( ) ) ;
2020-01-30 05:45:27 +03:00
}
void
2022-05-07 19:53:16 +03:00
ChatPage : : banUser ( const QString & room , QString userid , QString reason )
2020-01-30 05:45:27 +03:00
{
2022-03-06 17:59:32 +03:00
bool confirmed ;
reason =
QInputDialog : : getText ( nullptr ,
tr ( " Reason for the ban " ) ,
tr ( " Enter reason for banning %1 (%2) or hit enter for no reason: " )
. arg ( cache : : displayName ( room , userid ) , userid ) ,
QLineEdit : : Normal ,
reason ,
& confirmed ) ;
if ( ! confirmed ) {
2021-09-18 01:22:33 +03:00
return ;
2022-03-06 17:59:32 +03:00
}
2021-09-18 01:22:33 +03:00
http : : client ( ) - > ban_user (
room . toStdString ( ) ,
userid . toStdString ( ) ,
[ this , userid , room ] ( const mtx : : responses : : Empty & , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-12-29 00:30:12 +03:00
emit showNotification (
tr ( " Failed to ban %1 in %2: %3 " )
. arg ( userid , room , QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-09-18 01:22:33 +03:00
} else
emit showNotification ( tr ( " Banned user: %1 " ) . arg ( userid ) ) ;
} ,
reason . trimmed ( ) . toStdString ( ) ) ;
2020-01-30 05:45:27 +03:00
}
void
2022-05-07 19:53:16 +03:00
ChatPage : : unbanUser ( const QString & room , QString userid , QString reason )
2020-01-30 05:45:27 +03:00
{
2022-01-12 21:09:46 +03:00
if ( QMessageBox : : question ( nullptr ,
2021-09-18 01:22:33 +03:00
tr ( " Confirm unban " ) ,
tr ( " Do you really want to unban %1 (%2)? " )
2021-12-29 00:30:12 +03:00
. arg ( cache : : displayName ( room , userid ) , userid ) ) ! = QMessageBox : : Yes )
2021-09-18 01:22:33 +03:00
return ;
http : : client ( ) - > unban_user (
room . toStdString ( ) ,
userid . toStdString ( ) ,
[ this , userid , room ] ( const mtx : : responses : : Empty & , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-12-29 00:30:12 +03:00
emit showNotification (
tr ( " Failed to unban %1 in %2: %3 " )
. arg ( userid , room , QString : : fromStdString ( err - > matrix_error . error ) ) ) ;
2021-09-18 01:22:33 +03:00
} else
emit showNotification ( tr ( " Unbanned user: %1 " ) . arg ( userid ) ) ;
} ,
reason . trimmed ( ) . toStdString ( ) ) ;
2020-01-30 05:45:27 +03:00
}
2020-10-20 20:46:37 +03:00
void
ChatPage : : receivedSessionKey ( const std : : string & room_id , const std : : string & session_id )
{
2021-09-18 01:22:33 +03:00
view_manager_ - > receivedSessionKey ( room_id , session_id ) ;
2020-10-20 20:46:37 +03:00
}
2020-06-08 02:45:24 +03:00
QString
ChatPage : : status ( ) const
{
2021-12-30 06:54:03 +03:00
return QString : : fromStdString ( cache : : presence ( utils : : localUser ( ) . toStdString ( ) ) . status_msg ) ;
2020-06-08 02:45:24 +03:00
}
void
ChatPage : : setStatus ( const QString & status )
{
2021-09-18 01:22:33 +03:00
http : : client ( ) - > put_presence_status (
currentPresence ( ) , status . toStdString ( ) , [ ] ( mtx : : http : : RequestErr err ) {
if ( err ) {
nhlog : : net ( ) - > warn ( " failed to set presence status_msg: {} " , err - > matrix_error . error ) ;
}
} ) ;
2020-06-08 02:45:24 +03:00
}
2020-06-08 21:26:37 +03:00
mtx : : presence : : PresenceState
ChatPage : : currentPresence ( ) const
{
2021-09-18 01:22:33 +03:00
switch ( userSettings_ - > presence ( ) ) {
case UserSettings : : Presence : : Online :
return mtx : : presence : : online ;
case UserSettings : : Presence : : Unavailable :
return mtx : : presence : : unavailable ;
case UserSettings : : Presence : : Offline :
return mtx : : presence : : offline ;
2023-10-26 00:22:39 +03:00
case UserSettings : : Presence : : AutomaticPresence :
if ( lastWindowActive . isValid ( ) & &
lastWindowActive . addSecs ( 60 * 5 ) < QDateTime : : currentDateTime ( ) )
return mtx : : presence : : unavailable ;
else
return mtx : : presence : : online ;
2021-09-18 01:22:33 +03:00
default :
return mtx : : presence : : online ;
}
2020-06-08 21:26:37 +03:00
}
2021-09-06 01:07:14 +03:00
void
ChatPage : : verifyOneTimeKeyCountAfterStartup ( )
{
2021-09-18 01:22:33 +03:00
http : : client ( ) - > upload_keys (
olm : : client ( ) - > create_upload_keys_request ( ) ,
[ this ] ( const mtx : : responses : : UploadKeys & res , mtx : : http : : RequestErr err ) {
if ( err ) {
2021-11-29 08:20:43 +03:00
nhlog : : crypto ( ) - > warn ( " failed to update one-time keys: {} " , err ) ;
2021-09-18 01:22:33 +03:00
if ( err - > status_code < 400 | | err - > status_code > = 500 )
return ;
}
2023-10-31 18:38:15 +03:00
std : : map < std : : string_view , uint16_t > key_counts ;
2022-10-26 02:10:35 +03:00
std : : uint64_t count = 0 ;
2021-09-18 01:22:33 +03:00
if ( auto c = res . one_time_key_counts . find ( mtx : : crypto : : SIGNED_CURVE25519 ) ;
c = = res . one_time_key_counts . end ( ) ) {
key_counts [ mtx : : crypto : : SIGNED_CURVE25519 ] = 0 ;
} else {
2022-10-26 02:10:35 +03:00
key_counts [ mtx : : crypto : : SIGNED_CURVE25519 ] =
c - > second > std : : numeric_limits < std : : uint16_t > : : max ( )
? std : : numeric_limits < std : : uint16_t > : : max ( )
: static_cast < std : : uint16_t > ( c - > second ) ;
2022-10-26 02:14:46 +03:00
count = c - > second ;
2021-09-18 01:22:33 +03:00
}
nhlog : : crypto ( ) - > info (
" Fetched server key count {} {} " , count , mtx : : crypto : : SIGNED_CURVE25519 ) ;
2022-05-14 02:42:21 +03:00
ensureOneTimeKeyCount ( key_counts , std : : nullopt ) ;
2021-09-18 01:22:33 +03:00
} ) ;
2021-09-06 01:07:14 +03:00
}
2018-06-10 20:03:45 +03:00
void
2023-10-31 18:38:15 +03:00
ChatPage : : ensureOneTimeKeyCount ( const std : : map < std : : string_view , uint16_t > & counts ,
2022-05-14 02:42:21 +03:00
const std : : optional < std : : vector < std : : string > > & unused_fallback_keys )
2018-06-10 20:03:45 +03:00
{
2021-09-18 01:22:33 +03:00
if ( auto count = counts . find ( mtx : : crypto : : SIGNED_CURVE25519 ) ; count ! = counts . end ( ) ) {
2022-05-14 02:42:21 +03:00
bool replace_fallback_key = false ;
if ( unused_fallback_keys & &
std : : find ( unused_fallback_keys - > begin ( ) ,
unused_fallback_keys - > end ( ) ,
mtx : : crypto : : SIGNED_CURVE25519 ) = = unused_fallback_keys - > end ( ) )
replace_fallback_key = true ;
2021-09-18 01:22:33 +03:00
nhlog : : crypto ( ) - > debug (
2022-05-14 02:42:21 +03:00
" Updated server key count {} {}, fallback keys supported: {}, new fallback key: {} " ,
count - > second ,
mtx : : crypto : : SIGNED_CURVE25519 ,
unused_fallback_keys . has_value ( ) ,
replace_fallback_key ) ;
2021-09-18 01:22:33 +03:00
2022-05-14 02:42:21 +03:00
if ( count - > second < MAX_ONETIME_KEYS | | replace_fallback_key ) {
2022-05-21 16:21:14 +03:00
const size_t nkeys =
count - > second < MAX_ONETIME_KEYS ? ( MAX_ONETIME_KEYS - count - > second ) : 0 ;
2021-09-18 01:22:33 +03:00
nhlog : : crypto ( ) - > info ( " uploading {} {} keys " , nkeys , mtx : : crypto : : SIGNED_CURVE25519 ) ;
2022-05-14 02:42:21 +03:00
olm : : client ( ) - > generate_one_time_keys ( nkeys , replace_fallback_key ) ;
2021-09-18 01:22:33 +03:00
http : : client ( ) - > upload_keys (
olm : : client ( ) - > create_upload_keys_request ( ) ,
2022-05-14 02:42:21 +03:00
[ replace_fallback_key , this ] ( const mtx : : responses : : UploadKeys & ,
mtx : : http : : RequestErr err ) {
2021-09-18 01:22:33 +03:00
if ( err ) {
2021-11-29 08:20:43 +03:00
nhlog : : crypto ( ) - > warn ( " failed to update one-time keys: {} " , err ) ;
2018-06-10 20:03:45 +03:00
2021-09-18 01:22:33 +03:00
if ( err - > status_code < 400 | | err - > status_code > = 500 )
return ;
}
// mark as published anyway, otherwise we may end up in a loop.
olm : : mark_keys_as_published ( ) ;
2022-05-14 02:42:21 +03:00
if ( replace_fallback_key ) {
emit startRemoveFallbackKeyTimer ( ) ;
}
2021-09-18 01:22:33 +03:00
} ) ;
} else if ( count - > second > 2 * MAX_ONETIME_KEYS ) {
nhlog : : crypto ( ) - > warn ( " too many one-time keys, deleting 1 " ) ;
mtx : : requests : : ClaimKeys req ;
req . one_time_keys [ http : : client ( ) - > user_id ( ) . to_string ( ) ] [ http : : client ( ) - > device_id ( ) ] =
std : : string ( mtx : : crypto : : SIGNED_CURVE25519 ) ;
http : : client ( ) - > claim_keys (
req , [ ] ( const mtx : : responses : : ClaimKeys & , mtx : : http : : RequestErr err ) {
if ( err )
2021-11-29 08:20:43 +03:00
nhlog : : crypto ( ) - > warn ( " failed to clear 1 one-time key: {} " , err ) ;
2021-09-18 01:22:33 +03:00
else
nhlog : : crypto ( ) - > info ( " cleared 1 one-time key " ) ;
} ) ;
2018-06-10 20:03:45 +03:00
}
2021-09-18 01:22:33 +03:00
}
2018-06-10 20:03:45 +03:00
}
2018-06-12 20:36:16 +03:00
2022-05-14 02:42:21 +03:00
void
ChatPage : : removeOldFallbackKey ( )
{
olm : : client ( ) - > forget_old_fallback_key ( ) ;
olm : : mark_keys_as_published ( ) ;
}
2018-06-12 20:36:16 +03:00
void
ChatPage : : getProfileInfo ( )
{
2021-09-18 01:22:33 +03:00
const auto userid = utils : : localUser ( ) . toStdString ( ) ;
2018-06-12 20:36:16 +03:00
2021-09-18 01:22:33 +03:00
http : : client ( ) - > get_profile (
userid , [ this ] ( const mtx : : responses : : Profile & res , mtx : : http : : RequestErr err ) {
if ( err ) {
nhlog : : net ( ) - > warn ( " failed to retrieve own profile info " ) ;
return ;
}
2018-06-12 20:36:16 +03:00
2021-09-18 01:22:33 +03:00
emit setUserDisplayName ( QString : : fromStdString ( res . display_name ) ) ;
2018-06-12 20:36:16 +03:00
2021-09-18 01:22:33 +03:00
emit setUserAvatar ( QString : : fromStdString ( res . avatar_url ) ) ;
} ) ;
2018-07-04 00:05:05 +03:00
}
2018-08-31 09:10:47 +03:00
2021-08-17 04:23:51 +03:00
void
ChatPage : : getBackupVersion ( )
{
2021-09-18 01:22:33 +03:00
if ( ! UserSettings : : instance ( ) - > useOnlineKeyBackup ( ) ) {
nhlog : : crypto ( ) - > info ( " Online key backup disabled. " ) ;
return ;
}
http : : client ( ) - > backup_version (
[ this ] ( const mtx : : responses : : backup : : BackupVersion & res , mtx : : http : : RequestErr err ) {
if ( err ) {
nhlog : : net ( ) - > warn ( " Failed to retrieve backup version " ) ;
if ( err - > status_code = = 404 )
cache : : client ( ) - > deleteBackupVersion ( ) ;
return ;
}
// switch to UI thread for secrets stuff
2023-02-18 04:59:33 +03:00
QTimer : : singleShot ( 0 , this , [ this , res ] {
2021-09-18 01:22:33 +03:00
auto auth_data = nlohmann : : json : : parse ( res . auth_data ) ;
if ( res . algorithm = = " m.megolm_backup.v1.curve25519-aes-sha2 " ) {
auto key = cache : : secret ( mtx : : secret_storage : : secrets : : megolm_backup_v1 ) ;
if ( ! key ) {
nhlog : : crypto ( ) - > info ( " No key for online key backup. " ) ;
cache : : client ( ) - > deleteBackupVersion ( ) ;
return ;
2021-08-17 04:23:51 +03:00
}
2021-09-18 01:22:33 +03:00
using namespace mtx : : crypto ;
auto pubkey = CURVE25519_public_key_from_private ( to_binary_buf ( base642bin ( * key ) ) ) ;
if ( auth_data [ " public_key " ] . get < std : : string > ( ) ! = pubkey ) {
nhlog : : crypto ( ) - > info ( " Our backup key {} does not match the one "
2021-08-17 04:23:51 +03:00
" used in the online backup {} " ,
pubkey ,
2023-08-17 10:13:01 +03:00
auth_data [ " public_key " ] . get < std : : string > ( ) ) ;
2021-09-18 01:22:33 +03:00
cache : : client ( ) - > deleteBackupVersion ( ) ;
return ;
}
2023-02-18 04:59:33 +03:00
auto oldBackupVersion = cache : : client ( ) - > backupVersion ( ) ;
2021-09-18 01:22:33 +03:00
nhlog : : crypto ( ) - > info ( " Using online key backup. " ) ;
OnlineBackupVersion data { } ;
data . algorithm = res . algorithm ;
data . version = res . version ;
cache : : client ( ) - > saveBackupVersion ( data ) ;
2023-02-18 04:59:33 +03:00
if ( ! oldBackupVersion | | oldBackupVersion - > version ! = data . version ) {
view_manager_ - > rooms ( ) - > refetchOnlineKeyBackupKeys ( ) ;
}
2021-09-18 01:22:33 +03:00
} else {
nhlog : : crypto ( ) - > info ( " Unsupported key backup algorithm: {} " , res . algorithm ) ;
cache : : client ( ) - > deleteBackupVersion ( ) ;
}
2021-08-17 04:23:51 +03:00
} ) ;
2021-09-18 01:22:33 +03:00
} ) ;
2021-08-17 04:23:51 +03:00
}
2018-08-31 09:10:47 +03:00
void
ChatPage : : initiateLogout ( )
{
2021-09-18 01:22:33 +03:00
http : : client ( ) - > logout ( [ this ] ( const mtx : : responses : : Logout & , mtx : : http : : RequestErr err ) {
if ( err ) {
// TODO: handle special errors
emit contentLoaded ( ) ;
2021-11-29 08:20:43 +03:00
nhlog : : net ( ) - > warn ( " failed to logout: {} " , err ) ;
2021-09-18 01:22:33 +03:00
return ;
}
2018-08-31 09:10:47 +03:00
2021-09-18 01:22:33 +03:00
emit loggedOut ( ) ;
} ) ;
2018-08-31 09:10:47 +03:00
}
2020-08-24 11:26:50 +03:00
2020-07-11 02:19:48 +03:00
template < typename T >
void
ChatPage : : connectCallMessage ( )
{
2021-09-18 01:22:33 +03:00
connect ( callManager_ ,
qOverload < const QString & , const T & > ( & CallManager : : newMessage ) ,
view_manager_ ,
qOverload < const QString & , const T & > ( & TimelineViewManager : : queueCallMessage ) ) ;
2020-07-11 02:19:48 +03:00
}
2020-12-18 05:04:18 +03:00
void
ChatPage : : decryptDownloadedSecrets ( mtx : : secret_storage : : AesHmacSha2KeyDescription keyDesc ,
const SecretsToDecrypt & secrets )
{
2021-09-18 01:22:33 +03:00
QString text = QInputDialog : : getText (
2022-01-12 21:09:46 +03:00
nullptr ,
2021-09-18 01:22:33 +03:00
QCoreApplication : : translate ( " CrossSigningSecrets " , " Decrypt secrets " ) ,
keyDesc . name . empty ( )
? QCoreApplication : : translate (
" CrossSigningSecrets " , " Enter your recovery key or passphrase to decrypt your secrets: " )
: QCoreApplication : : translate (
" CrossSigningSecrets " ,
" Enter your recovery key or passphrase called %1 to decrypt your secrets: " )
. arg ( QString : : fromStdString ( keyDesc . name ) ) ,
QLineEdit : : Password ) ;
if ( text . isEmpty ( ) )
return ;
2021-12-15 20:25:10 +03:00
// strip space chars from a recovery key. It can't contain those, but some clients insert them
// to make them easier to read.
QString stripped = text ;
stripped . remove ( ' ' ) ;
stripped . remove ( ' \n ' ) ;
stripped . remove ( ' \t ' ) ;
2021-09-18 01:22:33 +03:00
2022-04-08 06:11:24 +03:00
auto decryptionKey = mtx : : crypto : : key_from_recoverykey ( stripped . toStdString ( ) , keyDesc ) ;
2021-09-18 01:22:33 +03:00
if ( ! decryptionKey & & keyDesc . passphrase ) {
try {
decryptionKey = mtx : : crypto : : key_from_passphrase ( text . toStdString ( ) , keyDesc ) ;
} catch ( std : : exception & e ) {
nhlog : : crypto ( ) - > error ( " Failed to derive secret key from passphrase: {} " , e . what ( ) ) ;
2020-12-18 05:04:18 +03:00
}
2021-09-18 01:22:33 +03:00
}
2020-12-18 05:04:18 +03:00
2021-09-18 01:22:33 +03:00
if ( ! decryptionKey ) {
QMessageBox : : information (
2022-01-12 21:09:46 +03:00
nullptr ,
2021-09-18 01:22:33 +03:00
QCoreApplication : : translate ( " CrossSigningSecrets " , " Decryption failed " ) ,
QCoreApplication : : translate ( " CrossSigningSecrets " ,
" Failed to decrypt secrets with the "
" provided recovery key or passphrase " ) ) ;
return ;
}
2021-10-30 01:22:47 +03:00
auto deviceKeys = cache : : client ( ) - > userKeys ( http : : client ( ) - > user_id ( ) . to_string ( ) ) ;
mtx : : requests : : KeySignaturesUpload req ;
2021-09-18 01:22:33 +03:00
for ( const auto & [ secretName , encryptedSecret ] : secrets ) {
auto decrypted = mtx : : crypto : : decrypt ( encryptedSecret , * decryptionKey , secretName ) ;
2022-04-08 06:11:24 +03:00
nhlog : : crypto ( ) - > debug ( " Secret {} decrypted: {} " , secretName , ! decrypted . empty ( ) ) ;
2021-10-30 01:22:47 +03:00
if ( ! decrypted . empty ( ) ) {
2021-09-18 01:22:33 +03:00
cache : : storeSecret ( secretName , decrypted ) ;
2021-10-30 01:22:47 +03:00
2021-11-08 18:26:16 +03:00
if ( deviceKeys & & deviceKeys - > device_keys . count ( http : : client ( ) - > device_id ( ) ) & &
2021-10-30 01:22:47 +03:00
secretName = = mtx : : secret_storage : : secrets : : cross_signing_self_signing ) {
auto myKey = deviceKeys - > device_keys . at ( http : : client ( ) - > device_id ( ) ) ;
if ( myKey . user_id = = http : : client ( ) - > user_id ( ) . to_string ( ) & &
myKey . device_id = = http : : client ( ) - > device_id ( ) & &
myKey . keys [ " ed25519: " + http : : client ( ) - > device_id ( ) ] = =
olm : : client ( ) - > identity_keys ( ) . ed25519 & &
myKey . keys [ " curve25519: " + http : : client ( ) - > device_id ( ) ] = =
olm : : client ( ) - > identity_keys ( ) . curve25519 ) {
2022-06-17 23:03:54 +03:00
nlohmann : : json j = myKey ;
2021-10-30 01:22:47 +03:00
j . erase ( " signatures " ) ;
j . erase ( " unsigned " ) ;
auto ssk = mtx : : crypto : : PkSigning : : from_seed ( decrypted ) ;
myKey . signatures [ http : : client ( ) - > user_id ( ) . to_string ( ) ]
[ " ed25519: " + ssk . public_key ( ) ] = ssk . sign ( j . dump ( ) ) ;
req . signatures [ http : : client ( ) - > user_id ( ) . to_string ( ) ]
[ http : : client ( ) - > device_id ( ) ] = myKey ;
}
} else if ( deviceKeys & &
secretName = = mtx : : secret_storage : : secrets : : cross_signing_master ) {
auto mk = mtx : : crypto : : PkSigning : : from_seed ( decrypted ) ;
if ( deviceKeys - > master_keys . user_id = = http : : client ( ) - > user_id ( ) . to_string ( ) & &
deviceKeys - > master_keys . keys [ " ed25519: " + mk . public_key ( ) ] = = mk . public_key ( ) ) {
2022-06-17 23:03:54 +03:00
nlohmann : : json j = deviceKeys - > master_keys ;
2021-10-30 01:22:47 +03:00
j . erase ( " signatures " ) ;
j . erase ( " unsigned " ) ;
2022-06-17 04:11:31 +03:00
mtx : : crypto : : CrossSigningKeys master_key =
j . get < mtx : : crypto : : CrossSigningKeys > ( ) ;
2021-10-30 01:22:47 +03:00
master_key . signatures [ http : : client ( ) - > user_id ( ) . to_string ( ) ]
[ " ed25519: " + http : : client ( ) - > device_id ( ) ] =
olm : : client ( ) - > sign_message ( j . dump ( ) ) ;
req . signatures [ http : : client ( ) - > user_id ( ) . to_string ( ) ] [ mk . public_key ( ) ] =
master_key ;
}
}
}
2021-09-18 01:22:33 +03:00
}
2021-10-30 01:22:47 +03:00
2022-04-08 06:11:24 +03:00
if ( ! req . signatures . empty ( ) ) {
2022-06-17 23:03:54 +03:00
nhlog : : crypto ( ) - > debug ( " Uploading new signatures: {} " , nlohmann : : json ( req ) . dump ( 2 ) ) ;
2021-10-30 01:22:47 +03:00
http : : client ( ) - > keys_signatures_upload (
req , [ ] ( const mtx : : responses : : KeySignaturesUpload & res , mtx : : http : : RequestErr err ) {
if ( err ) {
2023-01-09 04:06:49 +03:00
nhlog : : net ( ) - > error ( " failed to upload signatures: {} " , * err ) ;
2021-10-30 01:22:47 +03:00
}
for ( const auto & [ user_id , tmp ] : res . errors )
for ( const auto & [ key_id , e ] : tmp )
nhlog : : net ( ) - > error ( " signature error for user '{}' and key "
2021-11-29 08:20:43 +03:00
" id {}: {} {} " ,
2021-10-30 01:22:47 +03:00
user_id ,
key_id ,
mtx : : errors : : to_string ( e . errcode ) ,
e . error ) ;
} ) ;
2022-04-08 06:11:24 +03:00
}
2020-12-18 05:04:18 +03:00
}
2021-01-10 20:36:06 +03:00
void
2022-03-29 05:50:25 +03:00
ChatPage : : startChat ( QString userid , std : : optional < bool > encryptionEnabled )
2021-01-10 20:36:06 +03:00
{
2021-09-18 01:22:33 +03:00
auto joined_rooms = cache : : joinedRooms ( ) ;
auto room_infos = cache : : getRoomInfo ( joined_rooms ) ;
2021-12-29 00:30:12 +03:00
for ( const std : : string & room_id : joined_rooms ) {
2022-04-03 02:07:20 +03:00
if ( const auto & info = room_infos [ QString : : fromStdString ( room_id ) ] ;
info . member_count = = 2 & & ! info . is_space ) {
2021-09-18 01:22:33 +03:00
auto room_members = cache : : roomMembers ( room_id ) ;
if ( std : : find ( room_members . begin ( ) , room_members . end ( ) , ( userid ) . toStdString ( ) ) ! =
room_members . end ( ) ) {
view_manager_ - > rooms ( ) - > setCurrentRoom ( QString : : fromStdString ( room_id ) ) ;
2021-02-24 03:37:26 +03:00
return ;
2021-09-18 01:22:33 +03:00
}
2021-01-25 17:59:15 +03:00
}
2021-09-18 01:22:33 +03:00
}
if ( QMessageBox : : Yes ! =
QMessageBox : : question (
2022-01-12 21:09:46 +03:00
nullptr ,
2021-09-18 01:22:33 +03:00
tr ( " Confirm invite " ) ,
tr ( " Do you really want to start a private chat with %1? " ) . arg ( userid ) ) )
return ;
mtx : : requests : : CreateRoom req ;
2022-04-20 17:50:27 +03:00
req . preset = mtx : : requests : : Preset : : TrustedPrivateChat ;
2021-09-18 01:22:33 +03:00
req . visibility = mtx : : common : : RoomVisibility : : Private ;
2022-03-29 05:50:25 +03:00
2022-08-13 16:51:04 +03:00
if ( ! encryptionEnabled . has_value ( ) ) {
if ( auto keys = cache : : client ( ) - > userKeys ( userid . toStdString ( ) ) )
encryptionEnabled = ! keys - > device_keys . empty ( ) ;
}
2022-03-29 05:50:25 +03:00
if ( encryptionEnabled . value_or ( false ) ) {
mtx : : events : : StrippedEvent < mtx : : events : : state : : Encryption > enc ;
enc . type = mtx : : events : : EventType : : RoomEncryption ;
enc . content . algorithm = mtx : : crypto : : MEGOLM_ALGO ;
req . initial_state . emplace_back ( std : : move ( enc ) ) ;
}
2021-09-18 01:22:33 +03:00
if ( utils : : localUser ( ) ! = userid ) {
req . invite = { userid . toStdString ( ) } ;
req . is_direct = true ;
}
emit ChatPage : : instance ( ) - > createRoom ( req ) ;
2021-01-10 20:36:06 +03:00
}
static QString
2021-12-28 21:12:15 +03:00
mxidFromSegments ( QStringView sigil , QStringView mxid )
2021-01-10 20:36:06 +03:00
{
2021-09-18 01:22:33 +03:00
if ( mxid . isEmpty ( ) )
2021-12-29 06:28:08 +03:00
return QString ( ) ;
2021-09-18 01:22:33 +03:00
auto mxid_ = QUrl : : fromPercentEncoding ( mxid . toUtf8 ( ) ) ;
2021-12-28 21:12:15 +03:00
if ( sigil = = u " u " ) {
2021-09-18 01:22:33 +03:00
return " @ " + mxid_ ;
2021-12-28 21:12:15 +03:00
} else if ( sigil = = u " roomid " ) {
2021-09-18 01:22:33 +03:00
return " ! " + mxid_ ;
2021-12-28 21:12:15 +03:00
} else if ( sigil = = u " r " ) {
2021-09-18 01:22:33 +03:00
return " # " + mxid_ ;
//} else if (sigil == "group") {
// return "+" + mxid_;
} else {
2021-12-29 06:28:08 +03:00
return QString ( ) ;
2021-09-18 01:22:33 +03:00
}
2021-01-10 20:36:06 +03:00
}
2021-09-16 02:41:55 +03:00
bool
2021-12-17 07:53:34 +03:00
ChatPage : : handleMatrixUri ( QString uri )
2021-01-10 20:36:06 +03:00
{
2021-09-18 01:22:33 +03:00
nhlog : : ui ( ) - > info ( " Received uri! {} " , uri . toStdString ( ) ) ;
2021-12-17 07:53:34 +03:00
QUrl uri_ { uri } ;
2021-09-18 01:22:33 +03:00
// Convert matrix.to URIs to proper format
2021-12-29 06:28:08 +03:00
if ( uri_ . scheme ( ) = = QLatin1String ( " https " ) & & uri_ . host ( ) = = QLatin1String ( " matrix.to " ) ) {
2021-09-18 01:22:33 +03:00
QString p = uri_ . fragment ( QUrl : : FullyEncoded ) ;
2021-12-29 06:28:08 +03:00
if ( p . startsWith ( QLatin1String ( " / " ) ) )
2021-09-18 01:22:33 +03:00
p . remove ( 0 , 1 ) ;
2021-12-29 06:28:08 +03:00
auto temp = p . split ( QStringLiteral ( " ? " ) ) ;
2021-09-18 01:22:33 +03:00
QString query ;
if ( temp . size ( ) > = 2 )
query = QUrl : : fromPercentEncoding ( temp . takeAt ( 1 ) . toUtf8 ( ) ) ;
2021-12-29 06:28:08 +03:00
temp = temp . first ( ) . split ( QStringLiteral ( " / " ) ) ;
2021-09-18 01:22:33 +03:00
auto identifier = QUrl : : fromPercentEncoding ( temp . takeFirst ( ) . toUtf8 ( ) ) ;
QString eventId = QUrl : : fromPercentEncoding ( temp . join ( ' / ' ) . toUtf8 ( ) ) ;
if ( ! identifier . isEmpty ( ) ) {
2021-12-29 06:28:08 +03:00
if ( identifier . startsWith ( QLatin1String ( " @ " ) ) ) {
2021-09-18 01:22:33 +03:00
QByteArray newUri = " matrix:u/ " + QUrl : : toPercentEncoding ( identifier . remove ( 0 , 1 ) ) ;
if ( ! query . isEmpty ( ) )
newUri . append ( " ? " + query . toUtf8 ( ) ) ;
return handleMatrixUri ( QUrl : : fromEncoded ( newUri ) ) ;
2021-12-29 06:28:08 +03:00
} else if ( identifier . startsWith ( QLatin1String ( " # " ) ) ) {
2021-09-18 01:22:33 +03:00
QByteArray newUri = " matrix:r/ " + QUrl : : toPercentEncoding ( identifier . remove ( 0 , 1 ) ) ;
if ( ! eventId . isEmpty ( ) )
newUri . append ( " /e/ " + QUrl : : toPercentEncoding ( eventId . remove ( 0 , 1 ) ) ) ;
if ( ! query . isEmpty ( ) )
newUri . append ( " ? " + query . toUtf8 ( ) ) ;
return handleMatrixUri ( QUrl : : fromEncoded ( newUri ) ) ;
2021-12-29 06:28:08 +03:00
} else if ( identifier . startsWith ( QLatin1String ( " ! " ) ) ) {
2021-09-18 01:22:33 +03:00
QByteArray newUri =
" matrix:roomid/ " + QUrl : : toPercentEncoding ( identifier . remove ( 0 , 1 ) ) ;
if ( ! eventId . isEmpty ( ) )
newUri . append ( " /e/ " + QUrl : : toPercentEncoding ( eventId . remove ( 0 , 1 ) ) ) ;
if ( ! query . isEmpty ( ) )
newUri . append ( " ? " + query . toUtf8 ( ) ) ;
return handleMatrixUri ( QUrl : : fromEncoded ( newUri ) ) ;
}
2021-09-16 02:41:55 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-09-16 02:41:55 +03:00
2021-09-18 01:22:33 +03:00
// non-matrix URIs are not handled by us, return false
2021-12-29 06:28:08 +03:00
if ( uri_ . scheme ( ) ! = QLatin1String ( " matrix " ) )
2021-09-18 01:22:33 +03:00
return false ;
2021-01-10 20:36:06 +03:00
2021-09-18 01:22:33 +03:00
auto tempPath = uri_ . path ( QUrl : : ComponentFormattingOption : : FullyEncoded ) ;
if ( tempPath . startsWith ( ' / ' ) )
tempPath . remove ( 0 , 1 ) ;
2021-12-28 22:09:08 +03:00
auto segments = QStringView ( tempPath ) . split ( ' / ' ) ;
2021-01-10 20:36:06 +03:00
2021-09-18 01:22:33 +03:00
if ( segments . size ( ) ! = 2 & & segments . size ( ) ! = 4 )
return false ;
2021-01-10 20:36:06 +03:00
2021-09-18 01:22:33 +03:00
auto sigil1 = segments [ 0 ] ;
auto mxid1 = mxidFromSegments ( sigil1 , segments [ 1 ] ) ;
if ( mxid1 . isEmpty ( ) )
return false ;
QString mxid2 ;
2021-12-28 22:09:08 +03:00
if ( segments . size ( ) = = 4 & & segments [ 2 ] = = QStringView ( u " e " ) ) {
2021-09-18 01:22:33 +03:00
if ( segments [ 3 ] . isEmpty ( ) )
return false ;
else
mxid2 = " $ " + QUrl : : fromPercentEncoding ( segments [ 3 ] . toUtf8 ( ) ) ;
}
std : : vector < std : : string > vias ;
QString action ;
2021-12-29 00:30:12 +03:00
auto items =
uri_ . query ( QUrl : : ComponentFormattingOption : : FullyEncoded ) . split ( ' & ' , Qt : : SkipEmptyParts ) ;
2023-10-14 00:28:57 +03:00
for ( QString item : std : : as_const ( items ) ) {
2021-09-18 01:22:33 +03:00
nhlog : : ui ( ) - > info ( " item: {} " , item . toStdString ( ) ) ;
2021-12-29 06:28:08 +03:00
if ( item . startsWith ( QLatin1String ( " action= " ) ) ) {
action = item . remove ( QStringLiteral ( " action= " ) ) ;
} else if ( item . startsWith ( QLatin1String ( " via= " ) ) ) {
2021-12-29 08:01:38 +03:00
vias . push_back ( QUrl : : fromPercentEncoding ( item . remove ( QStringLiteral ( " via= " ) ) . toUtf8 ( ) )
. toStdString ( ) ) ;
2021-01-10 20:36:06 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-01-10 20:36:06 +03:00
2021-12-28 21:12:15 +03:00
if ( sigil1 = = u " u " ) {
2021-09-18 01:22:33 +03:00
if ( action . isEmpty ( ) ) {
2022-05-07 19:53:16 +03:00
auto t = MainWindow : : instance ( ) - > focusedRoom ( ) ;
2022-05-06 01:36:38 +03:00
if ( ! t . isEmpty ( ) & & cache : : isRoomMember ( mxid1 . toStdString ( ) , t . toStdString ( ) ) ) {
auto rm = view_manager_ - > rooms ( ) - > getRoomById ( t ) ;
if ( rm )
rm - > openUserProfile ( mxid1 ) ;
2021-09-16 02:41:55 +03:00
return true ;
2021-09-18 01:22:33 +03:00
}
emit view_manager_ - > openGlobalUserProfile ( mxid1 ) ;
2021-12-28 21:12:15 +03:00
} else if ( action = = u " chat " ) {
2021-09-18 01:22:33 +03:00
this - > startChat ( mxid1 ) ;
}
return true ;
2021-12-28 21:12:15 +03:00
} else if ( sigil1 = = u " roomid " ) {
2021-09-18 01:22:33 +03:00
auto joined_rooms = cache : : joinedRooms ( ) ;
auto targetRoomId = mxid1 . toStdString ( ) ;
2021-01-10 20:36:06 +03:00
2021-12-29 00:30:12 +03:00
for ( const auto & roomid : joined_rooms ) {
2021-09-18 01:22:33 +03:00
if ( roomid = = targetRoomId ) {
view_manager_ - > rooms ( ) - > setCurrentRoom ( mxid1 ) ;
if ( ! mxid2 . isEmpty ( ) )
view_manager_ - > showEvent ( mxid1 , mxid2 ) ;
return true ;
}
}
2021-01-10 20:36:06 +03:00
2021-12-28 21:12:15 +03:00
if ( action = = u " join " | | action . isEmpty ( ) ) {
2021-09-18 01:22:33 +03:00
joinRoomVia ( targetRoomId , vias ) ;
return true ;
2022-04-01 01:58:01 +03:00
} else if ( action = = u " knock " | | action . isEmpty ( ) ) {
knockRoom ( mxid1 , vias ) ;
return true ;
2021-09-18 01:22:33 +03:00
}
return false ;
2021-12-28 21:12:15 +03:00
} else if ( sigil1 = = u " r " ) {
2021-09-18 01:22:33 +03:00
auto joined_rooms = cache : : joinedRooms ( ) ;
auto targetRoomAlias = mxid1 . toStdString ( ) ;
2021-12-29 00:30:12 +03:00
for ( const auto & roomid : joined_rooms ) {
2022-06-16 02:49:51 +03:00
auto aliases =
cache : : client ( ) - > getStateEvent < mtx : : events : : state : : CanonicalAlias > ( roomid ) ;
2021-09-18 01:22:33 +03:00
if ( aliases ) {
2022-06-16 02:49:51 +03:00
if ( aliases - > content . alias = = targetRoomAlias ) {
2021-09-18 01:22:33 +03:00
view_manager_ - > rooms ( ) - > setCurrentRoom ( QString : : fromStdString ( roomid ) ) ;
if ( ! mxid2 . isEmpty ( ) )
view_manager_ - > showEvent ( QString : : fromStdString ( roomid ) , mxid2 ) ;
return true ;
2021-01-10 20:36:06 +03:00
}
2021-09-18 01:22:33 +03:00
}
}
2021-12-28 21:12:15 +03:00
if ( action = = u " join " | | action . isEmpty ( ) ) {
2021-09-18 01:22:33 +03:00
joinRoomVia ( mxid1 . toStdString ( ) , vias ) ;
return true ;
2022-04-01 01:58:01 +03:00
} else if ( action = = u " knock " | | action . isEmpty ( ) ) {
knockRoom ( mxid1 , vias ) ;
return true ;
2021-01-10 20:36:06 +03:00
}
2021-09-16 02:41:55 +03:00
return false ;
2021-09-18 01:22:33 +03:00
}
return false ;
2021-01-10 20:36:06 +03:00
}
2022-11-04 19:42:09 +03:00
void
ChatPage : : sendNotificationReply ( const QString & roomid , const QString & eventid , const QString & body )
{
view_manager_ - > queueReply ( roomid , eventid , body ) ;
auto exWin = MainWindow : : instance ( ) - > windowForRoom ( roomid ) ;
if ( exWin ) {
2023-02-01 20:24:25 +03:00
exWin - > setVisible ( true ) ;
exWin - > raise ( ) ;
2022-11-04 19:42:09 +03:00
exWin - > requestActivate ( ) ;
} else {
view_manager_ - > rooms ( ) - > setCurrentRoom ( roomid ) ;
2023-02-01 20:24:25 +03:00
MainWindow : : instance ( ) - > setVisible ( true ) ;
MainWindow : : instance ( ) - > raise ( ) ;
2022-11-04 19:42:09 +03:00
MainWindow : : instance ( ) - > requestActivate ( ) ;
}
}
2021-09-16 02:41:55 +03:00
bool
2021-01-10 20:36:06 +03:00
ChatPage : : handleMatrixUri ( const QUrl & uri )
{
2021-09-18 01:22:33 +03:00
return handleMatrixUri ( uri . toString ( QUrl : : ComponentFormattingOption : : FullyEncoded ) . toUtf8 ( ) ) ;
2021-01-10 20:36:06 +03:00
}
2021-02-22 21:48:31 +03:00
2021-05-29 00:25:57 +03:00
bool
ChatPage : : isRoomActive ( const QString & room_id )
{
2022-05-07 19:53:16 +03:00
return QGuiApplication : : focusWindow ( ) & & QGuiApplication : : focusWindow ( ) - > isActive ( ) & &
MainWindow : : instance ( ) - > windowForRoom ( room_id ) = = QGuiApplication : : focusWindow ( ) ;
2021-03-16 23:01:14 +03:00
}
2023-01-29 08:46:00 +03:00
void
ChatPage : : removeAllNotifications ( )
{
2023-02-22 07:09:16 +03:00
# if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
2023-01-29 08:46:00 +03:00
notificationsManager - > closeAllNotifications ( ) ;
# endif
2023-02-01 04:54:01 +03:00
}