2019-08-14 14:54:46 +00:00
/*
* Squawk messenger .
2019-08-21 09:35:07 +00:00
* Copyright ( C ) 2019 Yury Gubich < blue @ macaw . me >
2019-08-14 14:54:46 +00:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2019-03-29 14:54:34 +00:00
# include "account.h"
2019-06-28 15:15:30 +00:00
# include <QXmppMessage.h>
2019-04-07 14:02:41 +00:00
# include <QDateTime>
2019-03-29 14:54:34 +00:00
using namespace Core ;
2019-11-09 14:04:27 +00:00
Account : : Account ( const QString & p_login , const QString & p_server , const QString & p_password , const QString & p_name , NetworkAccess * p_net , QObject * parent ) :
2019-03-29 14:54:34 +00:00
QObject ( parent ) ,
2019-03-30 20:13:13 +00:00
name ( p_name ) ,
2020-06-20 22:26:30 +00:00
archiveQueries ( ) ,
2019-03-31 21:05:09 +00:00
client ( ) ,
2019-04-07 20:14:15 +00:00
config ( ) ,
presence ( ) ,
2020-04-03 22:28:15 +00:00
state ( Shared : : ConnectionState : : disconnected ) ,
2019-04-13 20:38:20 +00:00
cm ( new QXmppCarbonManager ( ) ) ,
2019-04-17 20:08:56 +00:00
am ( new QXmppMamManager ( ) ) ,
2019-07-11 08:51:52 +00:00
mm ( new QXmppMucManager ( ) ) ,
bm ( new QXmppBookmarkManager ( ) ) ,
2019-11-02 20:50:25 +00:00
rm ( client . findExtension < QXmppRosterManager > ( ) ) ,
vm ( client . findExtension < QXmppVCardManager > ( ) ) ,
2019-11-09 14:04:27 +00:00
um ( new QXmppUploadRequestManager ( ) ) ,
2019-11-12 13:38:01 +00:00
dm ( client . findExtension < QXmppDiscoveryManager > ( ) ) ,
2020-03-27 20:59:30 +00:00
rcpm ( new QXmppMessageReceiptManager ( ) ) ,
2020-05-21 15:42:40 +00:00
reconnectScheduled ( false ) ,
reconnectTimer ( new QTimer ) ,
2019-10-16 19:38:35 +00:00
avatarHash ( ) ,
avatarType ( ) ,
2019-11-09 14:04:27 +00:00
ownVCardRequestInProgress ( false ) ,
2020-03-25 15:28:36 +00:00
network ( p_net ) ,
2020-04-28 20:35:52 +00:00
passwordType ( Shared : : AccountPassword : : plain ) ,
2020-06-14 21:23:43 +00:00
mh ( new MessageHandler ( this ) ) ,
rh ( new RosterHandler ( this ) )
2019-03-29 14:54:34 +00:00
{
2019-04-07 20:14:15 +00:00
config . setUser ( p_login ) ;
config . setDomain ( p_server ) ;
config . setPassword ( p_password ) ;
2019-06-12 17:18:18 +00:00
config . setAutoAcceptSubscriptions ( true ) ;
2020-04-28 20:35:52 +00:00
//config.setAutoReconnectionEnabled(false);
2019-04-07 20:14:15 +00:00
2020-05-21 15:42:40 +00:00
QObject : : connect ( & client , & QXmppClient : : stateChanged , this , & Account : : onClientStateChange ) ;
2019-10-15 19:25:40 +00:00
QObject : : connect ( & client , & QXmppClient : : presenceReceived , this , & Account : : onPresenceReceived ) ;
2020-04-28 20:35:52 +00:00
QObject : : connect ( & client , & QXmppClient : : messageReceived , mh , & MessageHandler : : onMessageReceived ) ;
2019-10-15 19:25:40 +00:00
QObject : : connect ( & client , & QXmppClient : : error , this , & Account : : onClientError ) ;
2019-04-03 21:23:51 +00:00
2019-04-12 15:22:10 +00:00
client . addExtension ( cm ) ;
2020-04-28 20:35:52 +00:00
QObject : : connect ( cm , & QXmppCarbonManager : : messageReceived , mh , & MessageHandler : : onCarbonMessageReceived ) ;
QObject : : connect ( cm , & QXmppCarbonManager : : messageSent , mh , & MessageHandler : : onCarbonMessageSent ) ;
2019-04-13 20:38:20 +00:00
client . addExtension ( am ) ;
2019-10-15 19:25:40 +00:00
QObject : : connect ( am , & QXmppMamManager : : logMessage , this , & Account : : onMamLog ) ;
QObject : : connect ( am , & QXmppMamManager : : archivedMessageReceived , this , & Account : : onMamMessageReceived ) ;
QObject : : connect ( am , & QXmppMamManager : : resultsRecieved , this , & Account : : onMamResultsReceived ) ;
2019-07-11 08:51:52 +00:00
client . addExtension ( mm ) ;
client . addExtension ( bm ) ;
2019-10-15 19:25:40 +00:00
2019-11-02 20:50:25 +00:00
QObject : : connect ( vm , & QXmppVCardManager : : vCardReceived , this , & Account : : onVCardReceived ) ;
2019-10-16 19:38:35 +00:00
//QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
2019-11-09 14:04:27 +00:00
client . addExtension ( um ) ;
2020-04-28 20:35:52 +00:00
QObject : : connect ( um , & QXmppUploadRequestManager : : slotReceived , mh , & MessageHandler : : onUploadSlotReceived ) ;
QObject : : connect ( um , & QXmppUploadRequestManager : : requestFailed , mh , & MessageHandler : : onUploadSlotRequestFailed ) ;
2019-11-09 14:04:27 +00:00
2019-11-12 13:38:01 +00:00
QObject : : connect ( dm , & QXmppDiscoveryManager : : itemsReceived , this , & Account : : onDiscoveryItemsReceived ) ;
QObject : : connect ( dm , & QXmppDiscoveryManager : : infoReceived , this , & Account : : onDiscoveryInfoReceived ) ;
2020-04-28 20:35:52 +00:00
QObject : : connect ( network , & NetworkAccess : : uploadFileComplete , mh , & MessageHandler : : onFileUploaded ) ;
QObject : : connect ( network , & NetworkAccess : : uploadFileError , mh , & MessageHandler : : onFileUploadError ) ;
2019-11-11 15:19:54 +00:00
2020-03-27 20:59:30 +00:00
client . addExtension ( rcpm ) ;
2020-04-28 20:35:52 +00:00
QObject : : connect ( rcpm , & QXmppMessageReceiptManager : : messageDelivered , mh , & MessageHandler : : onReceiptReceived ) ;
2020-03-27 20:59:30 +00:00
2019-10-16 19:38:35 +00:00
QString path ( QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) ) ;
path + = " / " + name ;
QDir dir ( path ) ;
if ( ! dir . exists ( ) ) {
bool res = dir . mkpath ( path ) ;
if ( ! res ) {
qDebug ( ) < < " Couldn't create a cache directory for account " < < name ;
throw 22 ;
}
}
QFile * avatar = new QFile ( path + " /avatar.png " ) ;
QString type = " png " ;
if ( ! avatar - > exists ( ) ) {
delete avatar ;
avatar = new QFile ( path + " /avatar.jpg " ) ;
2019-10-24 09:42:38 +00:00
type = " jpg " ;
2019-10-16 19:38:35 +00:00
if ( ! avatar - > exists ( ) ) {
delete avatar ;
avatar = new QFile ( path + " /avatar.jpeg " ) ;
2019-10-24 09:42:38 +00:00
type = " jpeg " ;
2019-10-16 19:38:35 +00:00
if ( ! avatar - > exists ( ) ) {
delete avatar ;
avatar = new QFile ( path + " /avatar.gif " ) ;
2019-10-24 09:42:38 +00:00
type = " gif " ;
2019-10-16 19:38:35 +00:00
}
}
}
if ( avatar - > exists ( ) ) {
if ( avatar - > open ( QFile : : ReadOnly ) ) {
QCryptographicHash sha1 ( QCryptographicHash : : Sha1 ) ;
sha1 . addData ( avatar ) ;
avatarHash = sha1 . result ( ) ;
avatarType = type ;
}
}
if ( avatarType . size ( ) ! = 0 ) {
presence . setVCardUpdateType ( QXmppPresence : : VCardUpdateValidPhoto ) ;
presence . setPhotoHash ( avatarHash . toUtf8 ( ) ) ;
} else {
presence . setVCardUpdateType ( QXmppPresence : : VCardUpdateNotReady ) ;
}
2020-05-21 15:42:40 +00:00
reconnectTimer - > setSingleShot ( true ) ;
2020-06-20 22:26:30 +00:00
QObject : : connect ( reconnectTimer , & QTimer : : timeout , this , & Account : : onReconnectTimer ) ;
2020-05-21 15:42:40 +00:00
// QXmppLogger* logger = new QXmppLogger(this);
// logger->setLoggingType(QXmppLogger::SignalLogging);
// client.setLogger(logger);
//
// QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
// qDebug() << text;
// });
2019-03-29 14:54:34 +00:00
}
Account : : ~ Account ( )
{
2020-05-21 15:42:40 +00:00
if ( reconnectScheduled ) {
reconnectScheduled = false ;
reconnectTimer - > stop ( ) ;
}
2020-04-28 20:35:52 +00:00
QObject : : disconnect ( network , & NetworkAccess : : uploadFileComplete , mh , & MessageHandler : : onFileUploaded ) ;
QObject : : disconnect ( network , & NetworkAccess : : uploadFileError , mh , & MessageHandler : : onFileUploadError ) ;
2019-11-11 15:19:54 +00:00
2020-06-14 21:23:43 +00:00
delete mh ;
delete rh ;
2019-08-21 09:35:07 +00:00
2020-05-21 15:42:40 +00:00
delete reconnectTimer ;
2020-03-27 20:59:30 +00:00
delete rcpm ;
delete dm ;
2019-11-09 14:04:27 +00:00
delete um ;
2019-07-11 08:51:52 +00:00
delete bm ;
delete mm ;
delete am ;
delete cm ;
2019-03-31 21:05:09 +00:00
}
2019-03-29 14:54:34 +00:00
2019-03-31 21:05:09 +00:00
Shared : : ConnectionState Core : : Account : : getState ( ) const
{
return state ;
2019-03-29 14:54:34 +00:00
}
2019-03-31 21:05:09 +00:00
void Core : : Account : : connect ( )
{
2020-06-20 22:26:30 +00:00
if ( reconnectScheduled ) {
reconnectScheduled = false ;
reconnectTimer - > stop ( ) ;
}
2020-04-03 22:28:15 +00:00
if ( state = = Shared : : ConnectionState : : disconnected ) {
2019-05-24 14:46:34 +00:00
client . connectToServer ( config , presence ) ;
2019-03-31 21:05:09 +00:00
} else {
qDebug ( " An attempt to connect an account which is already connected, skipping " ) ;
}
}
2020-06-20 22:26:30 +00:00
void Core : : Account : : onReconnectTimer ( )
{
if ( reconnectScheduled ) {
reconnectScheduled = false ;
connect ( ) ;
}
}
2019-03-31 21:05:09 +00:00
void Core : : Account : : disconnect ( )
{
2020-05-21 15:42:40 +00:00
if ( reconnectScheduled ) {
reconnectScheduled = false ;
reconnectTimer - > stop ( ) ;
}
2020-04-03 22:28:15 +00:00
if ( state ! = Shared : : ConnectionState : : disconnected ) {
2020-06-20 22:26:30 +00:00
//rh->clearConferences();
2019-04-02 21:58:43 +00:00
client . disconnectFromServer ( ) ;
2019-03-31 21:05:09 +00:00
}
}
2020-05-21 15:42:40 +00:00
void Core : : Account : : onClientStateChange ( QXmppClient : : State st )
2019-03-31 21:05:09 +00:00
{
2020-05-21 15:42:40 +00:00
switch ( st ) {
case QXmppClient : : ConnectedState : {
if ( state ! = Shared : : ConnectionState : : connected ) {
if ( client . isActive ( ) ) {
Shared : : ConnectionState os = state ;
state = Shared : : ConnectionState : : connected ;
if ( os = = Shared : : ConnectionState : : connecting ) {
2020-08-07 23:33:03 +00:00
qDebug ( ) < < " running service discovery for account " < < name ;
2020-05-21 15:42:40 +00:00
dm - > requestItems ( getServer ( ) ) ;
dm - > requestInfo ( getServer ( ) ) ;
}
emit connectionStateChanged ( state ) ;
}
} else {
qDebug ( ) < < " Something weird happened - xmpp client of account " < < name
< < " reported about successful connection but account was in " < < state < < " state " ;
}
2019-05-24 14:46:34 +00:00
}
2020-05-21 15:42:40 +00:00
break ;
case QXmppClient : : ConnectingState : {
if ( state ! = Shared : : ConnectionState : : connecting ) {
state = Shared : : ConnectionState : : connecting ;
emit connectionStateChanged ( state ) ;
}
}
break ;
case QXmppClient : : DisconnectedState : {
if ( state ! = Shared : : ConnectionState : : disconnected ) {
2020-08-07 23:33:03 +00:00
handleDisconnection ( ) ;
2020-05-21 15:42:40 +00:00
state = Shared : : ConnectionState : : disconnected ;
emit connectionStateChanged ( state ) ;
} else {
qDebug ( ) < < " Something weird happened - xmpp client of account " < < name
< < " reported about disconnection but account was in " < < state < < " state " ;
}
}
break ;
2019-03-31 21:05:09 +00:00
}
}
2019-05-24 14:46:34 +00:00
void Core : : Account : : reconnect ( )
{
2020-05-21 15:42:40 +00:00
if ( state = = Shared : : ConnectionState : : connected & & ! reconnectScheduled ) {
reconnectScheduled = true ;
reconnectTimer - > start ( 500 ) ;
2019-05-24 14:46:34 +00:00
client . disconnectFromServer ( ) ;
} else {
qDebug ( ) < < " An attempt to reconnect account " < < getName ( ) < < " which was not connected " ;
}
}
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getName ( ) const {
return name ; }
2019-03-31 21:05:09 +00:00
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getLogin ( ) const {
return config . user ( ) ; }
2019-03-31 21:05:09 +00:00
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getPassword ( ) const {
return config . password ( ) ; }
2019-04-02 21:58:43 +00:00
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getServer ( ) const {
return config . domain ( ) ; }
2019-04-03 21:23:51 +00:00
2020-06-14 21:23:43 +00:00
Shared : : AccountPassword Core : : Account : : getPasswordType ( ) const {
return passwordType ; }
2020-04-04 16:40:32 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setPasswordType ( Shared : : AccountPassword pt ) {
passwordType = pt ; }
2019-04-06 10:14:32 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setLogin ( const QString & p_login ) {
config . setUser ( p_login ) ; }
2019-04-06 10:14:32 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setName ( const QString & p_name ) {
name = p_name ; }
2020-04-04 16:40:32 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setPassword ( const QString & p_password ) {
config . setPassword ( p_password ) ; }
2019-04-19 09:12:12 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setServer ( const QString & p_server ) {
config . setDomain ( p_server ) ; }
2019-04-06 10:14:32 +00:00
2020-06-14 21:23:43 +00:00
Shared : : Availability Core : : Account : : getAvailability ( ) const
2019-04-06 10:14:32 +00:00
{
2020-06-14 21:23:43 +00:00
if ( state = = Shared : : ConnectionState : : connected ) {
QXmppPresence : : AvailableStatusType pres = presence . availableStatusType ( ) ;
return static_cast < Shared : : Availability > ( pres ) ; //they are compatible;
} else {
return Shared : : Availability : : offline ;
2019-04-06 10:14:32 +00:00
}
}
2020-06-14 21:23:43 +00:00
void Core : : Account : : setAvailability ( Shared : : Availability avail )
2019-04-06 10:14:32 +00:00
{
2020-06-14 21:23:43 +00:00
if ( avail = = Shared : : Availability : : offline ) {
disconnect ( ) ; //TODO not sure how to do here - changing state may cause connection or disconnection
2019-04-19 09:12:12 +00:00
} else {
2020-06-14 21:23:43 +00:00
QXmppPresence : : AvailableStatusType pres = static_cast < QXmppPresence : : AvailableStatusType > ( avail ) ;
2019-04-19 09:12:12 +00:00
2020-06-14 21:23:43 +00:00
presence . setAvailableStatusType ( pres ) ;
if ( state ! = Shared : : ConnectionState : : disconnected ) {
client . setClientPresence ( presence ) ;
2019-04-19 09:12:12 +00:00
}
2019-04-03 21:23:51 +00:00
}
}
2019-10-16 19:38:35 +00:00
void Core : : Account : : onPresenceReceived ( const QXmppPresence & p_presence )
2019-04-07 14:02:41 +00:00
{
2019-10-16 19:38:35 +00:00
QString id = p_presence . from ( ) ;
2019-04-07 14:02:41 +00:00
QStringList comps = id . split ( " / " ) ;
2020-07-26 19:41:30 +00:00
QString jid = comps . front ( ) . toLower ( ) ;
2019-04-07 14:02:41 +00:00
QString resource = comps . back ( ) ;
2019-04-07 20:14:15 +00:00
QString myJid = getLogin ( ) + " @ " + getServer ( ) ;
if ( jid = = myJid ) {
if ( resource = = getResource ( ) ) {
2020-04-03 22:28:15 +00:00
emit availabilityChanged ( static_cast < Shared : : Availability > ( p_presence . availableStatusType ( ) ) ) ;
2019-04-07 20:14:15 +00:00
} else {
2019-10-16 19:38:35 +00:00
if ( ! ownVCardRequestInProgress ) {
switch ( p_presence . vCardUpdateType ( ) ) {
case QXmppPresence : : VCardUpdateNone : //this presence has nothing to do with photo
break ;
case QXmppPresence : : VCardUpdateNotReady : //let's say the photo didn't change here
break ;
case QXmppPresence : : VCardUpdateNoPhoto : //there is no photo, need to drop if any
if ( avatarType . size ( ) > 0 ) {
2019-11-02 20:50:25 +00:00
vm - > requestClientVCard ( ) ;
2019-10-16 19:38:35 +00:00
ownVCardRequestInProgress = true ;
}
break ;
case QXmppPresence : : VCardUpdateValidPhoto : //there is a photo, need to load
if ( avatarHash ! = p_presence . photoHash ( ) ) {
2019-11-02 20:50:25 +00:00
vm - > requestClientVCard ( ) ;
2019-10-16 19:38:35 +00:00
ownVCardRequestInProgress = true ;
}
break ;
}
}
2019-04-07 20:14:15 +00:00
}
2019-10-15 19:25:40 +00:00
} else {
2020-06-14 21:23:43 +00:00
RosterItem * item = rh - > getRosterItem ( jid ) ;
if ( item ! = 0 ) {
item - > handlePresence ( p_presence ) ;
2019-10-15 19:25:40 +00:00
}
2019-04-07 20:14:15 +00:00
}
2019-10-16 19:38:35 +00:00
switch ( p_presence . type ( ) ) {
2019-04-07 14:02:41 +00:00
case QXmppPresence : : Error :
2019-10-16 19:38:35 +00:00
qDebug ( ) < < " An error reported by presence from " < < id < < p_presence . error ( ) . text ( ) ;
2019-04-07 14:02:41 +00:00
break ;
case QXmppPresence : : Available : {
2019-10-16 19:38:35 +00:00
QDateTime lastInteraction = p_presence . lastUserInteraction ( ) ;
2019-04-07 14:02:41 +00:00
if ( ! lastInteraction . isValid ( ) ) {
2020-03-30 21:17:10 +00:00
lastInteraction = QDateTime : : currentDateTimeUtc ( ) ;
2019-04-07 14:02:41 +00:00
}
emit addPresence ( jid , resource , {
{ " lastActivity " , lastInteraction } ,
2019-10-16 19:38:35 +00:00
{ " availability " , p_presence . availableStatusType ( ) } , //TODO check and handle invisible
{ " status " , p_presence . statusText ( ) }
2019-04-07 14:02:41 +00:00
} ) ;
}
2020-06-14 21:23:43 +00:00
break ;
2019-04-07 14:02:41 +00:00
case QXmppPresence : : Unavailable :
emit removePresence ( jid , resource ) ;
break ;
case QXmppPresence : : Subscribe :
qDebug ( " xmpp presence \" subscribe \" received, do not yet know what to do, skipping " ) ;
case QXmppPresence : : Subscribed :
qDebug ( " xmpp presence \" subscribed \" received, do not yet know what to do, skipping " ) ;
case QXmppPresence : : Unsubscribe :
qDebug ( " xmpp presence \" unsubscribe \" received, do not yet know what to do, skipping " ) ;
case QXmppPresence : : Unsubscribed :
qDebug ( " xmpp presence \" unsubscribed \" received, do not yet know what to do, skipping " ) ;
case QXmppPresence : : Probe :
qDebug ( " xmpp presence \" probe \" received, do not yet know what to do, skipping " ) ;
break ;
}
}
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getResource ( ) const {
return config . resource ( ) ; }
2019-04-07 14:02:41 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : setResource ( const QString & p_resource ) {
config . setResource ( p_resource ) ; }
2019-04-07 20:14:15 +00:00
2020-06-14 21:23:43 +00:00
QString Core : : Account : : getFullJid ( ) const {
return getLogin ( ) + " @ " + getServer ( ) + " / " + getResource ( ) ; }
2019-04-07 20:14:15 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : sendMessage ( const Shared : : Message & data ) {
mh - > sendMessage ( data ) ; }
2019-04-07 20:14:15 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : sendMessage ( const Shared : : Message & data , const QString & path ) {
mh - > sendMessage ( data , path ) ; }
2019-05-15 17:36:37 +00:00
void Core : : Account : : onMamMessageReceived ( const QString & queryId , const QXmppMessage & msg )
2019-04-13 20:38:20 +00:00
{
2019-09-10 14:33:39 +00:00
if ( msg . id ( ) . size ( ) > 0 & & ( msg . body ( ) . size ( ) > 0 | | msg . outOfBandUrl ( ) . size ( ) > 0 ) ) {
2020-06-20 22:26:30 +00:00
std : : map < QString , QString > : : const_iterator itr = archiveQueries . find ( queryId ) ;
if ( itr ! = archiveQueries . end ( ) ) {
2020-04-19 13:13:15 +00:00
QString jid = itr - > second ;
2020-06-14 21:23:43 +00:00
RosterItem * item = rh - > getRosterItem ( jid ) ;
2020-04-19 13:13:15 +00:00
Shared : : Message sMsg ( static_cast < Shared : : Message : : Type > ( msg . type ( ) ) ) ;
2020-04-28 20:35:52 +00:00
mh - > initializeMessage ( sMsg , msg , false , true , true ) ;
2020-04-19 13:13:15 +00:00
sMsg . setState ( Shared : : Message : : State : : sent ) ;
QString oId = msg . replaceId ( ) ;
if ( oId . size ( ) > 0 ) {
item - > correctMessageInArchive ( oId , sMsg ) ;
} else {
item - > addMessageToArchive ( sMsg ) ;
}
2020-03-28 14:05:49 +00:00
}
2019-08-21 09:35:07 +00:00
}
2020-03-26 15:08:44 +00:00
}
void Core : : Account : : requestArchive ( const QString & jid , int count , const QString & before )
{
qDebug ( ) < < " An archive request for " < < jid < < " , before " < < before ;
2020-06-14 21:23:43 +00:00
RosterItem * contact = rh - > getRosterItem ( jid ) ;
2020-03-26 15:08:44 +00:00
2019-08-21 09:35:07 +00:00
if ( contact = = 0 ) {
2019-04-21 19:17:04 +00:00
qDebug ( ) < < " An attempt to request archive for " < < jid < < " in account " < < name < < " , but the contact with such id wasn't found, skipping " ;
2020-04-19 13:13:15 +00:00
emit responseArchive ( jid , std : : list < Shared : : Message > ( ) ) ;
2019-04-21 19:17:04 +00:00
return ;
}
2020-04-19 13:13:15 +00:00
if ( state ! = Shared : : ConnectionState : : connected ) {
qDebug ( ) < < " An attempt to request archive for " < < jid < < " in account " < < name < < " , but the account is not online, skipping " ;
emit responseArchive ( contact - > jid , std : : list < Shared : : Message > ( ) ) ;
}
2020-05-21 15:42:40 +00:00
contact - > requestHistory ( count , before ) ;
2019-05-15 17:36:37 +00:00
}
2019-07-01 06:31:38 +00:00
void Core : : Account : : onContactNeedHistory ( const QString & before , const QString & after , const QDateTime & at )
2019-05-15 17:36:37 +00:00
{
2019-08-14 14:54:46 +00:00
RosterItem * contact = static_cast < RosterItem * > ( sender ( ) ) ;
2020-05-21 15:42:40 +00:00
2020-05-22 16:28:26 +00:00
QString to ;
QString with ;
2019-04-13 20:38:20 +00:00
QXmppResultSetQuery query ;
2020-05-21 15:42:40 +00:00
QDateTime start ;
2019-04-13 20:38:20 +00:00
query . setMax ( 100 ) ;
2020-05-21 15:42:40 +00:00
if ( contact - > getArchiveState ( ) = = RosterItem : : empty ) {
2019-05-15 17:36:37 +00:00
query . setBefore ( before ) ;
2020-05-21 15:42:40 +00:00
qDebug ( ) < < " Requesting remote history from empty for " < < contact - > jid ;
} else {
if ( before . size ( ) > 0 ) {
query . setBefore ( before ) ;
}
if ( after . size ( ) > 0 ) { //there is some strange behavior of ejabberd server returning empty result set
if ( at . isValid ( ) ) { //there can be some useful information about it here https://github.com/processone/ejabberd/issues/2924
start = at ;
} else {
query . setAfter ( after ) ;
}
2019-07-01 06:31:38 +00:00
}
2020-05-21 15:42:40 +00:00
qDebug ( ) < < " Remote query for " < < contact - > jid < < " from " < < after < < " , to " < < before ;
2019-05-15 17:36:37 +00:00
}
2020-05-22 16:28:26 +00:00
if ( contact - > isMuc ( ) ) {
to = contact - > jid ;
} else {
with = contact - > jid ;
}
QString q = am - > retrieveArchivedMessages ( to , " " , with , start , QDateTime ( ) , query ) ;
2020-06-20 22:26:30 +00:00
archiveQueries . insert ( std : : make_pair ( q , contact - > jid ) ) ;
2019-04-13 20:38:20 +00:00
}
void Core : : Account : : onMamResultsReceived ( const QString & queryId , const QXmppResultSetReply & resultSetReply , bool complete )
{
2020-06-20 22:26:30 +00:00
std : : map < QString , QString > : : const_iterator itr = archiveQueries . find ( queryId ) ;
if ( itr ! = archiveQueries . end ( ) ) {
2020-04-19 13:13:15 +00:00
QString jid = itr - > second ;
2020-06-20 22:26:30 +00:00
archiveQueries . erase ( itr ) ;
2020-04-19 13:13:15 +00:00
2020-06-14 21:23:43 +00:00
RosterItem * ri = rh - > getRosterItem ( jid ) ;
2020-04-19 13:13:15 +00:00
if ( ri ! = 0 ) {
2020-05-21 15:42:40 +00:00
qDebug ( ) < < " Flushing messages for " < < jid < < " , complete: " < < complete ;
2020-04-19 13:13:15 +00:00
ri - > flushMessagesToArchive ( complete , resultSetReply . first ( ) , resultSetReply . last ( ) ) ;
}
2019-04-13 20:38:20 +00:00
}
}
2019-04-19 09:12:12 +00:00
2019-06-28 15:15:30 +00:00
void Core : : Account : : onMamLog ( QXmppLogger : : MessageType type , const QString & msg )
{
qDebug ( ) < < " MAM MESSAGE LOG:: " ;
qDebug ( ) < < msg ;
}
2019-05-15 17:36:37 +00:00
void Core : : Account : : onClientError ( QXmppClient : : Error err )
{
2020-04-13 19:57:23 +00:00
qDebug ( ) < < " Error " ;
2019-05-24 14:46:34 +00:00
QString errorText ;
QString errorType ;
2019-05-15 17:36:37 +00:00
switch ( err ) {
case QXmppClient : : SocketError :
2019-05-24 14:46:34 +00:00
errorText = client . socketErrorString ( ) ;
errorType = " Client socket error " ;
2019-05-15 17:36:37 +00:00
break ;
2019-05-24 14:46:34 +00:00
case QXmppClient : : XmppStreamError : {
QXmppStanza : : Error : : Condition cnd = client . xmppStreamError ( ) ;
switch ( cnd ) {
case QXmppStanza : : Error : : BadRequest :
errorText = " Bad request " ;
break ;
case QXmppStanza : : Error : : Conflict :
errorText = " Conflict " ;
break ;
case QXmppStanza : : Error : : FeatureNotImplemented :
errorText = " Feature is not implemented " ;
break ;
case QXmppStanza : : Error : : Forbidden :
errorText = " Forbidden " ;
break ;
case QXmppStanza : : Error : : Gone :
errorText = " Gone " ;
break ;
case QXmppStanza : : Error : : InternalServerError :
errorText = " Internal server error " ;
break ;
case QXmppStanza : : Error : : ItemNotFound :
errorText = " Item was not found " ;
break ;
case QXmppStanza : : Error : : JidMalformed :
errorText = " Malformed JID " ;
break ;
case QXmppStanza : : Error : : NotAcceptable :
errorText = " Not acceptable " ;
break ;
case QXmppStanza : : Error : : NotAllowed :
errorText = " Not allowed " ;
break ;
case QXmppStanza : : Error : : NotAuthorized :
errorText = " Authentication error " ;
break ;
case QXmppStanza : : Error : : PaymentRequired :
errorText = " Payment is required " ;
break ;
case QXmppStanza : : Error : : RecipientUnavailable :
errorText = " Recipient is unavailable " ;
break ;
case QXmppStanza : : Error : : Redirect :
errorText = " Redirected " ;
break ;
case QXmppStanza : : Error : : RegistrationRequired :
errorText = " Registration is required " ;
break ;
case QXmppStanza : : Error : : RemoteServerNotFound :
errorText = " Remote server was not found " ;
break ;
case QXmppStanza : : Error : : RemoteServerTimeout :
errorText = " Remote server timeout " ;
break ;
case QXmppStanza : : Error : : ResourceConstraint :
errorText = " Resource constraint " ;
break ;
case QXmppStanza : : Error : : ServiceUnavailable :
errorText = " Redirected " ;
break ;
case QXmppStanza : : Error : : SubscriptionRequired :
errorText = " Subscription is required " ;
break ;
case QXmppStanza : : Error : : UndefinedCondition :
errorText = " Undefined condition " ;
2020-06-20 22:26:30 +00:00
reconnectScheduled = true ;
reconnectTimer - > start ( 500 ) ; //let's reconnect here just for now, it seems to be something broken in QXMPP
2019-05-24 14:46:34 +00:00
break ;
case QXmppStanza : : Error : : UnexpectedRequest :
errorText = " Unexpected request " ;
break ;
2020-04-15 17:27:38 +00:00
# if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
2020-04-13 19:57:23 +00:00
case QXmppStanza : : Error : : PolicyViolation :
errorText = " Policy violation " ;
break ;
2020-04-14 19:40:32 +00:00
# endif
2019-05-24 14:46:34 +00:00
}
errorType = " Client stream error " ;
}
2019-05-15 17:36:37 +00:00
break ;
case QXmppClient : : KeepAliveError :
2019-05-24 14:46:34 +00:00
errorText = " Client keep alive error " ;
2019-05-15 17:36:37 +00:00
break ;
2019-11-17 10:24:12 +00:00
case QXmppClient : : NoError :
break ; //not exactly sure what to do here
2019-05-15 17:36:37 +00:00
}
2019-05-24 14:46:34 +00:00
qDebug ( ) < < errorType < < errorText ;
emit error ( errorText ) ;
2019-05-15 17:36:37 +00:00
}
2019-06-12 17:18:18 +00:00
void Core : : Account : : subscribeToContact ( const QString & jid , const QString & reason )
{
2020-04-03 22:28:15 +00:00
if ( state = = Shared : : ConnectionState : : connected ) {
2019-11-02 20:50:25 +00:00
rm - > subscribe ( jid , reason ) ;
2019-06-12 17:18:18 +00:00
} else {
2019-06-14 16:36:04 +00:00
qDebug ( ) < < " An attempt to subscribe account " < < name < < " to contact " < < jid < < " but the account is not in the connected state, skipping " ;
2019-06-12 17:18:18 +00:00
}
}
void Core : : Account : : unsubscribeFromContact ( const QString & jid , const QString & reason )
{
2020-04-03 22:28:15 +00:00
if ( state = = Shared : : ConnectionState : : connected ) {
2019-11-02 20:50:25 +00:00
rm - > unsubscribe ( jid , reason ) ;
2019-06-12 17:18:18 +00:00
} else {
2019-06-14 16:36:04 +00:00
qDebug ( ) < < " An attempt to unsubscribe account " < < name < < " from contact " < < jid < < " but the account is not in the connected state, skipping " ;
2019-06-12 17:18:18 +00:00
}
}
2020-06-14 21:23:43 +00:00
void Core : : Account : : removeContactRequest ( const QString & jid ) {
rh - > removeContactRequest ( jid ) ; }
2019-07-11 08:51:52 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : addContactRequest ( const QString & jid , const QString & name , const QSet < QString > & groups ) {
rh - > addContactRequest ( jid , name , groups ) ; }
2019-08-29 14:19:35 +00:00
void Core : : Account : : setRoomAutoJoin ( const QString & jid , bool joined )
{
2020-06-14 21:23:43 +00:00
Conference * conf = rh - > getConference ( jid ) ;
if ( conf = = 0 ) {
2019-08-29 14:19:35 +00:00
qDebug ( ) < < " An attempt to set auto join to the non existing room " < < jid < < " of the account " < < getName ( ) < < " , skipping " ;
return ;
}
2020-06-14 21:23:43 +00:00
conf - > setAutoJoin ( joined ) ;
2019-08-29 14:19:35 +00:00
}
void Core : : Account : : setRoomJoined ( const QString & jid , bool joined )
{
2020-06-14 21:23:43 +00:00
Conference * conf = rh - > getConference ( jid ) ;
if ( conf = = 0 ) {
2019-08-29 14:19:35 +00:00
qDebug ( ) < < " An attempt to set joined to the non existing room " < < jid < < " of the account " < < getName ( ) < < " , skipping " ;
return ;
}
2020-06-14 21:23:43 +00:00
conf - > setJoined ( joined ) ;
2019-08-29 14:19:35 +00:00
}
2019-09-01 19:46:12 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : removeRoomRequest ( const QString & jid ) {
rh - > removeRoomRequest ( jid ) ; }
2019-09-01 19:46:12 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : addRoomRequest ( const QString & jid , const QString & nick , const QString & password , bool autoJoin ) {
rh - > addRoomRequest ( jid , nick , password , autoJoin ) ; }
2019-09-01 19:46:12 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : addContactToGroupRequest ( const QString & jid , const QString & groupName ) {
rh - > addContactToGroupRequest ( jid , groupName ) ; }
2019-09-03 20:28:58 +00:00
2020-06-14 21:23:43 +00:00
void Core : : Account : : removeContactFromGroupRequest ( const QString & jid , const QString & groupName ) {
rh - > removeContactFromGroupRequest ( jid , groupName ) ; }
2019-10-01 08:47:40 +00:00
void Core : : Account : : renameContactRequest ( const QString & jid , const QString & newName )
{
2020-06-14 21:23:43 +00:00
Contact * cnt = rh - > getContact ( jid ) ;
if ( cnt = = 0 ) {
2019-10-01 08:47:40 +00:00
qDebug ( ) < < " An attempt to rename non existing contact " < < jid < < " of account " < < name < < " , skipping " ;
} else {
2019-11-02 20:50:25 +00:00
rm - > renameItem ( jid , newName ) ;
2019-10-01 08:47:40 +00:00
}
}
2019-10-15 19:25:40 +00:00
void Core : : Account : : onVCardReceived ( const QXmppVCardIq & card )
{
2019-12-30 20:22:04 +00:00
QString id = card . from ( ) ;
QStringList comps = id . split ( " / " ) ;
2020-07-26 19:41:30 +00:00
QString jid = comps . front ( ) . toLower ( ) ;
2019-12-30 20:22:04 +00:00
QString resource ( " " ) ;
if ( comps . size ( ) > 1 ) {
resource = comps . back ( ) ;
}
pendingVCardRequests . erase ( id ) ;
2020-06-14 21:23:43 +00:00
RosterItem * item = rh - > getRosterItem ( jid ) ;
2019-10-15 19:25:40 +00:00
2020-03-26 15:08:44 +00:00
if ( item = = 0 ) {
if ( jid = = getLogin ( ) + " @ " + getServer ( ) ) {
onOwnVCardReceived ( card ) ;
2019-10-15 19:25:40 +00:00
} else {
2020-03-26 15:08:44 +00:00
qDebug ( ) < < " received vCard " < < jid < < " doesn't belong to any of known contacts or conferences, skipping " ;
2019-10-15 19:25:40 +00:00
}
2020-03-26 15:08:44 +00:00
return ;
2019-10-15 19:25:40 +00:00
}
2019-12-30 20:22:04 +00:00
Shared : : VCard vCard = item - > handleResponseVCard ( card , resource ) ;
2019-10-19 19:34:25 +00:00
emit receivedVCard ( jid , vCard ) ;
2019-10-15 19:25:40 +00:00
}
2019-10-16 19:38:35 +00:00
void Core : : Account : : onOwnVCardReceived ( const QXmppVCardIq & card )
{
QByteArray ava = card . photo ( ) ;
2019-10-24 09:42:38 +00:00
bool avaChanged = false ;
2019-10-16 19:38:35 +00:00
QString path = QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) + " / " + name + " / " ;
if ( ava . size ( ) > 0 ) {
QCryptographicHash sha1 ( QCryptographicHash : : Sha1 ) ;
sha1 . addData ( ava ) ;
QString newHash ( sha1 . result ( ) ) ;
QMimeDatabase db ;
QMimeType newType = db . mimeTypeForData ( ava ) ;
if ( avatarType . size ( ) > 0 ) {
if ( avatarHash ! = newHash ) {
QString oldPath = path + " avatar. " + avatarType ;
QFile oldAvatar ( oldPath ) ;
bool oldToRemove = false ;
if ( oldAvatar . exists ( ) ) {
if ( oldAvatar . rename ( oldPath + " .bak " ) ) {
oldToRemove = true ;
} else {
qDebug ( ) < < " Received new avatar for account " < < name < < " but can't get rid of the old one, doing nothing " ;
}
}
QFile newAvatar ( path + " avatar. " + newType . preferredSuffix ( ) ) ;
if ( newAvatar . open ( QFile : : WriteOnly ) ) {
newAvatar . write ( ava ) ;
newAvatar . close ( ) ;
avatarHash = newHash ;
avatarType = newType . preferredSuffix ( ) ;
2019-10-24 09:42:38 +00:00
avaChanged = true ;
2019-10-16 19:38:35 +00:00
} else {
qDebug ( ) < < " Received new avatar for account " < < name < < " but can't save it " ;
if ( oldToRemove ) {
qDebug ( ) < < " rolling back to the old avatar " ;
if ( ! oldAvatar . rename ( oldPath ) ) {
qDebug ( ) < < " Couldn't roll back to the old avatar in account " < < name ;
}
}
}
}
} else {
QFile newAvatar ( path + " avatar. " + newType . preferredSuffix ( ) ) ;
if ( newAvatar . open ( QFile : : WriteOnly ) ) {
newAvatar . write ( ava ) ;
newAvatar . close ( ) ;
avatarHash = newHash ;
avatarType = newType . preferredSuffix ( ) ;
2019-10-24 09:42:38 +00:00
avaChanged = true ;
2019-10-16 19:38:35 +00:00
} else {
qDebug ( ) < < " Received new avatar for account " < < name < < " but can't save it " ;
}
}
} else {
if ( avatarType . size ( ) > 0 ) {
QFile oldAvatar ( path + " avatar. " + avatarType ) ;
if ( ! oldAvatar . remove ( ) ) {
qDebug ( ) < < " Received vCard for account " < < name < < " without avatar, but can't get rid of the file, doing nothing " ;
} else {
2019-10-24 09:42:38 +00:00
avatarType = " " ;
avatarHash = " " ;
avaChanged = true ;
2019-10-16 19:38:35 +00:00
}
}
}
2019-10-24 09:42:38 +00:00
if ( avaChanged ) {
2019-10-16 19:38:35 +00:00
QMap < QString , QVariant > change ;
if ( avatarType . size ( ) > 0 ) {
presence . setPhotoHash ( avatarHash . toUtf8 ( ) ) ;
presence . setVCardUpdateType ( QXmppPresence : : VCardUpdateValidPhoto ) ;
change . insert ( " avatarPath " , path + " avatar. " + avatarType ) ;
} else {
presence . setPhotoHash ( " " ) ;
presence . setVCardUpdateType ( QXmppPresence : : VCardUpdateNoPhoto ) ;
change . insert ( " avatarPath " , " " ) ;
}
client . setClientPresence ( presence ) ;
2019-10-24 09:42:38 +00:00
emit changed ( change ) ;
2019-10-16 19:38:35 +00:00
}
ownVCardRequestInProgress = false ;
2019-10-19 19:34:25 +00:00
Shared : : VCard vCard ;
2019-11-05 18:55:21 +00:00
initializeVCard ( vCard , card ) ;
2019-10-19 19:34:25 +00:00
if ( avatarType . size ( ) > 0 ) {
vCard . setAvatarType ( Shared : : Avatar : : valid ) ;
vCard . setAvatarPath ( path + " avatar. " + avatarType ) ;
} else {
vCard . setAvatarType ( Shared : : Avatar : : empty ) ;
}
emit receivedVCard ( getLogin ( ) + " @ " + getServer ( ) , vCard ) ;
2019-10-16 19:38:35 +00:00
}
QString Core : : Account : : getAvatarPath ( ) const
{
2019-12-24 08:42:46 +00:00
if ( avatarType . size ( ) = = 0 ) {
return " " ;
} else {
return QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) + " / " + name + " / " + " avatar. " + avatarType ;
}
2019-10-16 19:38:35 +00:00
}
2019-10-22 15:13:56 +00:00
void Core : : Account : : requestVCard ( const QString & jid )
{
if ( pendingVCardRequests . find ( jid ) = = pendingVCardRequests . end ( ) ) {
2019-12-30 20:22:04 +00:00
qDebug ( ) < < " requesting vCard " < < jid ;
2019-10-22 15:13:56 +00:00
if ( jid = = getLogin ( ) + " @ " + getServer ( ) ) {
if ( ! ownVCardRequestInProgress ) {
2019-11-02 20:50:25 +00:00
vm - > requestClientVCard ( ) ;
2019-10-22 15:13:56 +00:00
ownVCardRequestInProgress = true ;
}
} else {
2019-11-02 20:50:25 +00:00
vm - > requestVCard ( jid ) ;
2019-10-22 15:13:56 +00:00
pendingVCardRequests . insert ( jid ) ;
}
}
}
2019-10-24 09:42:38 +00:00
void Core : : Account : : uploadVCard ( const Shared : : VCard & card )
{
QXmppVCardIq iq ;
2019-11-05 18:55:21 +00:00
initializeQXmppVCard ( iq , card ) ;
2019-11-04 15:22:39 +00:00
2019-10-24 09:42:38 +00:00
bool avatarChanged = false ;
2019-11-17 10:24:12 +00:00
if ( card . getAvatarType ( ) ! = Shared : : Avatar : : empty ) {
2019-10-24 09:42:38 +00:00
QString newPath = card . getAvatarPath ( ) ;
QString oldPath = getAvatarPath ( ) ;
QByteArray data ;
QString type ;
if ( newPath ! = oldPath ) {
QFile avatar ( newPath ) ;
if ( ! avatar . open ( QFile : : ReadOnly ) ) {
qDebug ( ) < < " An attempt to upload new vCard to account " < < name
< < " but it wasn't possible to read file " < < newPath
< < " which was supposed to be new avatar, uploading old avatar " ;
if ( avatarType . size ( ) > 0 ) {
QFile oA ( oldPath ) ;
if ( ! oA . open ( QFile : : ReadOnly ) ) {
qDebug ( ) < < " Couldn't read old avatar of account " < < name < < " , uploading empty avatar " ;
} else {
data = oA . readAll ( ) ;
}
}
} else {
data = avatar . readAll ( ) ;
avatarChanged = true ;
}
} else {
if ( avatarType . size ( ) > 0 ) {
QFile oA ( oldPath ) ;
if ( ! oA . open ( QFile : : ReadOnly ) ) {
qDebug ( ) < < " Couldn't read old avatar of account " < < name < < " , uploading empty avatar " ;
} else {
data = oA . readAll ( ) ;
}
}
}
if ( data . size ( ) > 0 ) {
QMimeDatabase db ;
type = db . mimeTypeForData ( data ) . name ( ) ;
iq . setPhoto ( data ) ;
iq . setPhotoType ( type ) ;
}
}
2019-11-02 20:50:25 +00:00
vm - > setClientVCard ( iq ) ;
2019-10-24 09:42:38 +00:00
onOwnVCardReceived ( iq ) ;
}
2019-11-09 14:04:27 +00:00
2019-11-12 13:38:01 +00:00
void Core : : Account : : onDiscoveryItemsReceived ( const QXmppDiscoveryIq & items )
{
for ( QXmppDiscoveryIq : : Item item : items . items ( ) ) {
2020-03-27 20:59:30 +00:00
if ( item . jid ( ) ! = getServer ( ) ) {
dm - > requestInfo ( item . jid ( ) ) ;
}
2019-11-12 13:38:01 +00:00
}
}
void Core : : Account : : onDiscoveryInfoReceived ( const QXmppDiscoveryIq & info )
{
2020-08-07 23:33:03 +00:00
qDebug ( ) < < " Discovery info received for account " < < name ;
2020-03-27 20:59:30 +00:00
if ( info . from ( ) = = getServer ( ) ) {
if ( info . features ( ) . contains ( " urn:xmpp:carbons:2 " ) ) {
2020-08-07 23:33:03 +00:00
qDebug ( ) < < " Enabling carbon copies for account " < < name ;
2020-03-27 20:59:30 +00:00
cm - > setCarbonsEnabled ( true ) ;
}
}
}
2019-11-12 13:38:01 +00:00
2020-08-07 23:33:03 +00:00
void Core : : Account : : handleDisconnection ( )
2020-04-19 13:13:15 +00:00
{
2020-08-07 23:33:03 +00:00
cm - > setCarbonsEnabled ( false ) ;
2020-06-20 22:26:30 +00:00
rh - > handleOffline ( ) ;
archiveQueries . clear ( ) ;
pendingVCardRequests . clear ( ) ;
Shared : : VCard vCard ; //just to show, that there is now more pending request
for ( const QString & jid : pendingVCardRequests ) {
emit receivedVCard ( jid , vCard ) ; //need to show it better in the future, like with an error
}
2020-08-07 23:33:03 +00:00
pendingVCardRequests . clear ( ) ;
2020-06-20 22:26:30 +00:00
ownVCardRequestInProgress = false ;
2020-04-19 13:13:15 +00:00
}