2020-10-11 11:48:51 +02:00
# include "receiving.h"
# include "client-utils.h"
# include "format.h"
# include "purple-info.h"
# include "file-transfer.h"
2020-10-12 19:48:44 +02:00
# include "sticker.h"
2020-10-11 11:48:51 +02:00
# include "config.h"
2020-10-12 20:23:29 +02:00
# include "call.h"
2021-01-03 01:54:17 +01:00
# include <algorithm>
2020-10-11 11:48:51 +02:00
2021-01-03 13:18:33 +01:00
enum {
HISTORY_MESSAGES_ABSOLUTE_LIMIT = 10000
} ;
2020-10-11 11:48:51 +02:00
std : : string makeNoticeWithSender ( const td : : td_api : : chat & chat , const TgMessageInfo & message ,
const char * noticeText , PurpleAccount * account )
{
std : : string prefix = getSenderDisplayName ( chat , message , account ) ;
if ( ! prefix . empty ( ) )
prefix + = " : " ;
return prefix + noticeText ;
}
std : : string getMessageText ( const td : : td_api : : formattedText & text )
{
char * newText = purple_markup_escape_text ( text . text_ . c_str ( ) , text . text_ . size ( ) ) ;
std : : string result ( newText ) ;
g_free ( newText ) ;
return result ;
}
std : : string makeInlineImageText ( int imgstoreId )
{
return " \n <img id= \" " + std : : to_string ( imgstoreId ) + " \" > " ;
}
static PurpleMessageFlags getNotificationFlags ( PurpleMessageFlags extraFlags )
{
unsigned flags = ( extraFlags & PURPLE_MESSAGE_ERROR ) | ( extraFlags & PURPLE_MESSAGE_NO_LOG ) ;
if ( flags = = 0 )
flags = PURPLE_MESSAGE_SYSTEM ;
return ( PurpleMessageFlags ) flags ;
}
2021-01-01 14:14:16 +01:00
void sendConversationReadReceipts ( TdAccountData & account , PurpleConversation * conv )
{
if ( ! conversationHasFocus ( conv ) )
return ;
2021-01-01 14:47:21 +01:00
ChatId chatId ;
PurpleConversationType convType = purple_conversation_get_type ( conv ) ;
const char * convName = purple_conversation_get_name ( conv ) ;
if ( convType = = PURPLE_CONV_TYPE_IM ) {
UserId privateChatUserId = purpleBuddyNameToUserId ( convName ) ;
SecretChatId secretChatId = purpleBuddyNameToSecretChatId ( convName ) ;
const td : : td_api : : chat * tdlibChat = nullptr ;
if ( privateChatUserId . valid ( ) )
tdlibChat = account . getPrivateChatByUserId ( privateChatUserId ) ;
else if ( secretChatId . valid ( ) )
tdlibChat = account . getChatBySecretChat ( secretChatId ) ;
if ( tdlibChat )
chatId = getId ( * tdlibChat ) ;
} else if ( convType = = PURPLE_CONV_TYPE_CHAT )
chatId = getTdlibChatId ( convName ) ;
std : : vector < ReadReceipt > receipts ;
account . extractPendingReadReceipts ( chatId , receipts ) ;
if ( ! receipts . empty ( ) ) {
purple_debug_misc ( config : : pluginId , " Sending %zu read receipts for chat % " G_GINT64_FORMAT " \n " ,
receipts . size ( ) , chatId . value ( ) ) ;
td : : td_api : : object_ptr < td : : td_api : : viewMessages > viewMessagesReq = td : : td_api : : make_object < td : : td_api : : viewMessages > ( ) ;
viewMessagesReq - > chat_id_ = chatId . value ( ) ;
viewMessagesReq - > force_read_ = true ; // no idea what "closed chats" are at this point
viewMessagesReq - > message_ids_ . resize ( receipts . size ( ) ) ;
for ( size_t i = 0 ; i < receipts . size ( ) ; i + + )
viewMessagesReq - > message_ids_ [ i ] = receipts [ i ] . messageId . value ( ) ;
account . transceiver . sendQuery ( std : : move ( viewMessagesReq ) , nullptr ) ;
}
2021-01-01 14:14:16 +01:00
}
2020-10-11 11:48:51 +02:00
void showMessageTextIm ( TdAccountData & account , const char * purpleUserName , const char * text ,
const char * notification , time_t timestamp , PurpleMessageFlags flags )
{
PurpleConversation * conv = NULL ;
if ( text ) {
if ( flags & PURPLE_MESSAGE_SEND ) {
// serv_got_im seems to work for messages sent from another client, but not for
// echoed messages from this client. Therefore, this (code snippet from facebook plugin).
conv = getImConversation ( account . purpleAccount , purpleUserName ) ;
purple_conv_im_write ( purple_conversation_get_im_data ( conv ) ,
purple_account_get_name_for_display ( account . purpleAccount ) ,
text , flags , timestamp ) ;
} else {
serv_got_im ( purple_account_get_connection ( account . purpleAccount ) , purpleUserName , text ,
flags , timestamp ) ;
2021-01-01 14:14:16 +01:00
conv = getImConversation ( account . purpleAccount , purpleUserName ) ;
2020-10-11 11:48:51 +02:00
}
}
if ( notification ) {
if ( conv = = NULL )
conv = getImConversation ( account . purpleAccount , purpleUserName ) ;
purple_conv_im_write ( purple_conversation_get_im_data ( conv ) , purpleUserName , notification ,
getNotificationFlags ( flags ) , timestamp ) ;
}
2021-01-01 14:14:16 +01:00
2021-01-01 16:40:44 +01:00
// TODO: sending all pending read receipts for the chat is technically not quite right,
// because maybe a message is being shown while others are waiting for some asynchronous
// response before they can be displayed. But who cares.
2021-01-01 14:14:16 +01:00
if ( conv ! = NULL )
sendConversationReadReceipts ( account , conv ) ;
2020-10-11 11:48:51 +02:00
}
static void showMessageTextChat ( TdAccountData & account , const td : : td_api : : chat & chat ,
const TgMessageInfo & message , const char * text ,
const char * notification , PurpleMessageFlags flags )
{
// Again, doing what facebook plugin does
int purpleId = account . getPurpleChatId ( getId ( chat ) ) ;
PurpleConvChat * conv = getChatConversation ( account , chat , purpleId ) ;
if ( text ) {
if ( flags & PURPLE_MESSAGE_SEND ) {
if ( conv )
purple_conv_chat_write ( conv , purple_account_get_name_for_display ( account . purpleAccount ) ,
text , flags , message . timestamp ) ;
} else {
if ( purpleId ! = 0 )
serv_got_chat_in ( purple_account_get_connection ( account . purpleAccount ) , purpleId ,
message . incomingGroupchatSender . empty ( ) ? " someone " : message . incomingGroupchatSender . c_str ( ) ,
flags , text , message . timestamp ) ;
}
}
if ( notification ) {
if ( conv )
// Protocol plugins mostly use who="" for such messages, but this currently causes problems
// with Spectrum. Use some non-empty string. Pidgin will ignore the who parameter for
// notification messages.
purple_conv_chat_write ( conv , " " , notification , getNotificationFlags ( flags ) , message . timestamp ) ;
}
2021-01-01 14:14:16 +01:00
2021-01-01 16:40:44 +01:00
// TODO: sending all pending read receipts for the chat is technically not quite right,
// because maybe a message is being shown while others are waiting for some asynchronous
// response before they can be displayed. But who cares.
2021-01-01 14:14:16 +01:00
PurpleConversation * baseConv = conv ? purple_conv_chat_get_conversation ( conv ) : NULL ;
if ( baseConv ! = NULL )
sendConversationReadReceipts ( account , baseConv ) ;
2020-10-11 11:48:51 +02:00
}
static std : : string quoteMessage ( const td : : td_api : : message * message , TdAccountData & account )
{
const td : : td_api : : user * originalAuthor = nullptr ;
if ( message )
originalAuthor = account . getUser ( getSenderUserId ( * message ) ) ;
std : : string originalName ;
if ( originalAuthor )
originalName = account . getDisplayName ( * originalAuthor ) ;
else {
// message == NULL means it could not be fetched, or took too long to fetch
// TRANSLATOR: In-line placeholder if the original author of a quote is unknown. Is at the beginning of the line if and only if you make it so, see "<b>&bt {} wrote:"...
originalName = _ ( " Unknown user " ) ;
}
std : : string text ;
if ( ! message | | ! message - > content_ ) {
// TRANSLATOR: In-chat placeholder when something unknown is being replied to.
text = _ ( " [message unavailable] " ) ;
} else switch ( message - > content_ - > get_id ( ) ) {
case td : : td_api : : messageText : : ID : {
const td : : td_api : : messageText & messageText = static_cast < const td : : td_api : : messageText & > ( * message - > content_ ) ;
if ( messageText . text_ )
text = getMessageText ( * messageText . text_ ) ;
else
text = " " ;
break ;
}
case td : : td_api : : messagePhoto : : ID : {
const td : : td_api : : messagePhoto & photo = static_cast < const td : : td_api : : messagePhoto & > ( * message - > content_ ) ;
// TRANSLATOR: In-line placeholder when a photo is being replied to.
text = _ ( " [photo] " ) ;
if ( photo . caption_ )
text + = " " + photo . caption_ - > text_ ;
break ;
}
case td : : td_api : : messageDocument : : ID : {
const td : : td_api : : messageDocument & document = static_cast < const td : : td_api : : messageDocument & > ( * message - > content_ ) ;
if ( document . document_ ) {
// TRANSLATOR: In-line placeholder when a file is being replied to. Arguments will be the file name and MIME type (e.g. "application/gzip")
text = formatMessage ( _ ( " [file: {0} ({1})] " ) , { document . document_ - > file_name_ ,
document . document_ - > mime_type_ } ) ;
} else {
// Not supposed to be possible, but just in case
text = " [file] " ;
}
if ( document . caption_ )
text + = " " + document . caption_ - > text_ ;
break ;
}
case td : : td_api : : messageVideo : : ID : {
const td : : td_api : : messageVideo & video = static_cast < const td : : td_api : : messageVideo & > ( * message - > content_ ) ;
if ( video . video_ ) {
// TRANSLATOR: In-line placeholder when a video is being replied to. Argument will be the file name.
text = formatMessage ( _ ( " [video: {}] " ) , video . video_ - > file_name_ ) ;
} else {
// Not supposed to be possible, but just in case
text = " [video] " ;
}
if ( video . caption_ )
text + = " " + video . caption_ - > text_ ;
break ;
}
case td : : td_api : : messageSticker : : ID :
// TRANSLATOR: In-line placeholder when a sticker is being replied to.
text = _ ( " [sticker] " ) ;
break ;
default :
text = ' [ ' + getUnsupportedMessageDescription ( * message - > content_ ) + ' ] ' ;
break ;
}
for ( unsigned i = 0 ; i < text . size ( ) ; i + + )
if ( text [ i ] = = ' \n ' ) text [ i ] = ' ' ;
// TRANSLATOR: In-chat notification of a reply. Arguments will be username and the original text or description thereof. Please preserve the HTML.
return formatMessage ( _ ( " <b>> {0} wrote:</b> \n > {1} " ) , { originalName , text } ) ;
}
void showMessageText ( TdAccountData & account , const td : : td_api : : chat & chat , const TgMessageInfo & message ,
const char * text , const char * notification , uint32_t extraFlags )
{
PurpleMessageFlags directionFlag = message . outgoing ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV ;
PurpleMessageFlags flags = ( PurpleMessageFlags ) ( extraFlags | directionFlag ) ;
if ( message . outgoing & & ! message . sentLocally )
flags = ( PurpleMessageFlags ) ( flags | PURPLE_MESSAGE_REMOTE_SEND ) ;
std : : string newText ;
if ( text ) {
2020-10-12 18:32:38 +02:00
if ( message . repliedMessageId . valid ( ) )
newText = quoteMessage ( message . repliedMessage . get ( ) , account ) ;
if ( ! message . forwardedFrom . empty ( ) ) {
if ( ! newText . empty ( ) )
newText + = " \n " ;
// TRANSLATOR: In-chat notification of forward. Argument will be a username. Please preserve the HTML.
newText + = formatMessage ( _ ( " <b>Forwarded from {}:</b> " ) , message . forwardedFrom ) ;
}
2020-10-11 11:48:51 +02:00
if ( ! newText . empty ( ) )
newText + = " \n " ;
newText + = text ;
}
if ( ! newText . empty ( ) )
text = newText . c_str ( ) ;
const td : : td_api : : user * privateUser = account . getUserByPrivateChat ( chat ) ;
if ( privateUser ) {
2020-10-25 17:23:48 +01:00
std : : string userName = getPurpleBuddyName ( * privateUser ) ;
// If there is no buddy in the buddy list, libpurple won't be able to translate buddy name
// to alias, so use display name instead of idXXXXXXXXX
if ( ! purple_find_buddy ( account . purpleAccount , userName . c_str ( ) ) )
2020-10-11 11:48:51 +02:00
userName = account . getDisplayName ( * privateUser ) ;
showMessageTextIm ( account , userName . c_str ( ) , text , notification , message . timestamp , flags ) ;
}
SecretChatId secretChatId = getSecretChatId ( chat ) ;
if ( secretChatId . valid ( ) ) {
std : : string userName = getSecretChatBuddyName ( secretChatId ) ;
showMessageTextIm ( account , userName . c_str ( ) , text , notification , message . timestamp , flags ) ;
}
if ( getBasicGroupId ( chat ) . valid ( ) | | getSupergroupId ( chat ) . valid ( ) )
showMessageTextChat ( account , chat , message , text , notification , flags ) ;
}
void showChatNotification ( TdAccountData & account , const td : : td_api : : chat & chat ,
2020-10-11 19:20:37 +02:00
const char * notification , time_t timestamp , PurpleMessageFlags extraFlags )
2020-10-11 11:48:51 +02:00
{
TgMessageInfo messageInfo ;
messageInfo . type = TgMessageInfo : : Type : : Other ;
2020-10-11 19:20:37 +02:00
messageInfo . timestamp = timestamp ;
2020-10-11 11:48:51 +02:00
messageInfo . outgoing = true ;
2020-10-11 19:20:37 +02:00
showMessageText ( account , chat , messageInfo , NULL , notification , extraFlags ) ;
}
void showChatNotification ( TdAccountData & account , const td : : td_api : : chat & chat ,
const char * notification , PurpleMessageFlags extraFlags )
{
showChatNotification ( account , chat , notification ,
( extraFlags & PURPLE_MESSAGE_NO_LOG ) ? 0 : time ( NULL ) , extraFlags ) ;
2020-10-11 11:48:51 +02:00
}
2020-10-12 19:48:44 +02:00
static void showDownloadedImage ( const td : : td_api : : chat & chat , TgMessageInfo & message ,
const std : : string & filePath , const char * caption ,
TdAccountData & account )
{
std : : string text ;
std : : string notice ;
gchar * data = NULL ;
size_t len = 0 ;
if ( g_file_get_contents ( filePath . c_str ( ) , & data , & len , NULL ) ) {
int id = purple_imgstore_add_with_id ( data , len , NULL ) ;
text = makeInlineImageText ( id ) ;
} else if ( filePath . find ( ' " ' ) = = std : : string : : npos )
text = " <img src= \" file:// " + filePath + " \" > " ;
else {
// Unlikely error, not worth translating
notice = makeNoticeWithSender ( chat , message , " Cannot show photo: file path contains quotes " ,
account . purpleAccount ) ;
}
if ( caption & & * caption ) {
if ( ! text . empty ( ) )
text + = " \n " ;
text + = caption ;
}
showMessageText ( account , chat , message , text . empty ( ) ? NULL : text . c_str ( ) ,
notice . empty ( ) ? NULL : notice . c_str ( ) , PURPLE_MESSAGE_IMAGES ) ;
}
2020-10-12 22:41:13 +02:00
bool isStickerAnimated ( const std : : string & filePath )
2020-10-12 19:48:44 +02:00
{
2020-10-12 22:41:13 +02:00
return ( filePath . size ( ) > = 4 ) & & ! strcmp ( filePath . c_str ( ) + filePath . size ( ) - 4 , " .tgs " ) ;
}
bool shouldConvertAnimatedSticker ( const TgMessageInfo & message , const PurpleAccount * purpleAccount )
{
# ifndef NoLottie
return ! message . outgoing & &
purple_account_get_bool ( purpleAccount , AccountOptions : : AnimatedStickers ,
AccountOptions : : AnimatedStickersDefault ) ;
# else
return false ;
# endif
2020-10-12 19:48:44 +02:00
}
static void showDownloadedSticker ( const td : : td_api : : chat & chat , TgMessageInfo & message ,
const std : : string & filePath ,
const std : : string & fileDescription ,
td : : td_api : : object_ptr < td : : td_api : : file > thumbnail ,
TdTransceiver & transceiver , TdAccountData & account )
{
2020-10-12 22:41:13 +02:00
if ( isStickerAnimated ( filePath ) ) {
if ( shouldConvertAnimatedSticker ( message , account . purpleAccount ) ) {
2020-10-12 19:48:44 +02:00
// TRANSLATOR: In-chat status update
std : : string notice = makeNoticeWithSender ( chat , message , _ ( " Converting sticker " ) ,
account . purpleAccount ) ;
showMessageText ( account , chat , message , NULL , notice . c_str ( ) ) ;
StickerConversionThread * thread ;
thread = new StickerConversionThread ( account . purpleAccount , filePath , getId ( chat ) ,
std : : move ( message ) ) ;
thread - > startThread ( ) ;
} else if ( thumbnail ) {
// Avoid message like "Downloading sticker thumbnail...
// Also ignore size limits, but only determined testers and crazy people would notice.
if ( thumbnail - > local_ & & thumbnail - > local_ - > is_downloading_completed_ )
showDownloadedSticker ( chat , message , thumbnail - > local_ - > path_ ,
fileDescription , nullptr , transceiver , account ) ;
else
downloadFileInline ( thumbnail - > id_ , getId ( chat ) , message , fileDescription , nullptr ,
transceiver , account ) ;
} else {
2020-10-25 14:04:58 +01:00
showGenericFileInline ( chat , message , filePath , NULL , fileDescription , account ) ;
2020-10-12 19:48:44 +02:00
}
} else {
showWebpSticker ( chat , message , filePath , fileDescription , account ) ;
}
}
2020-10-11 11:48:51 +02:00
void showGenericFileInline ( const td : : td_api : : chat & chat , const TgMessageInfo & message ,
2020-10-25 14:04:58 +01:00
const std : : string & filePath , const char * caption ,
const std : : string & fileDescription , TdAccountData & account )
2020-10-11 11:48:51 +02:00
{
if ( filePath . find ( ' " ' ) ! = std : : string : : npos ) {
std : : string notice = makeNoticeWithSender ( chat , message , " Cannot show file: path contains quotes " ,
account . purpleAccount ) ;
2020-10-25 14:04:58 +01:00
showMessageText ( account , chat , message , caption , notice . c_str ( ) ) ;
2020-10-11 11:48:51 +02:00
} else {
std : : string text = " <a href= \" file:// " + filePath + " \" > " + fileDescription + " </a> " ;
2020-10-25 14:04:58 +01:00
if ( caption & & * caption ) {
text + = " \n " ;
text + = caption ;
}
2020-10-11 11:48:51 +02:00
showMessageText ( account , chat , message , text . c_str ( ) , NULL ) ;
}
}
2020-10-12 19:48:44 +02:00
void showDownloadedFileInline ( ChatId chatId , TgMessageInfo & message ,
const std : : string & filePath , const char * caption ,
const std : : string & fileDescription ,
td : : td_api : : object_ptr < td : : td_api : : file > thumbnail ,
TdTransceiver & transceiver , TdAccountData & account )
{
const td : : td_api : : chat * chat = account . getChat ( chatId ) ;
if ( ! chat ) return ;
switch ( message . type ) {
case TgMessageInfo : : Type : : Photo :
showDownloadedImage ( * chat , message , filePath , caption , account ) ;
break ;
case TgMessageInfo : : Type : : Sticker :
showDownloadedSticker ( * chat , message , filePath , fileDescription , std : : move ( thumbnail ) ,
transceiver , account ) ;
break ;
case TgMessageInfo : : Type : : Other :
2020-10-25 14:04:58 +01:00
showGenericFileInline ( * chat , message , filePath , caption , fileDescription , account ) ;
2020-10-12 19:48:44 +02:00
break ;
}
}
2020-10-12 20:23:29 +02:00
static void showTextMessage ( const td : : td_api : : chat & chat , const TgMessageInfo & message ,
const td : : td_api : : messageText & text , TdAccountData & account )
{
if ( text . text_ ) {
std : : string displayText = getMessageText ( * text . text_ ) ;
showMessageText ( account , chat , message , displayText . c_str ( ) , NULL ) ;
}
}
struct InlineDownloadInfo {
int32_t fileId ;
ChatId chatId ;
TgMessageInfo message ;
std : : string fileDescription ;
TdTransceiver * transceiver ;
TdAccountData * account ;
} ;
static void startInlineDownload ( void * user_data )
{
std : : unique_ptr < InlineDownloadInfo > info ( static_cast < InlineDownloadInfo * > ( user_data ) ) ;
downloadFileInline ( info - > fileId , info - > chatId , info - > message , info - > fileDescription ,
nullptr , * info - > transceiver , * info - > account ) ;
}
static void ignoreInlineDownload ( InlineDownloadInfo * info )
{
delete info ;
}
static void requestInlineDownload ( const char * sender , const td : : td_api : : file & file ,
const std : : string & fileDesc , const td : : td_api : : chat & chat ,
TgMessageInfo & message , TdTransceiver & transceiver , TdAccountData & account )
{
// TRANSLATOR: Download dialog, primary content, argument will be a username.
std : : string question = formatMessage ( _ ( " Download file from {}? " ) ,
getSenderDisplayName ( chat , message , account . purpleAccount ) ) ;
unsigned size = getFileSize ( file ) ;
// This dialog is used for files larger than the limit, so size should be non-zero
char * sizeStr = purple_str_size_to_units ( size ) ;
// TRANSLATOR: Download dialog, placeholder chat title, in the sentence "posted in a private chat".
std : : string chatName = isPrivateChat ( chat ) ? _ ( " a private chat " ) : chat . title_ ;
// TRANSLATOR: Download dialog, secondary content. Arguments will be file description (text), chat name (text), and a file size (text!)
std : : string fileInfo = formatMessage ( _ ( " {0} posted in {1}, size: {2} " ) , { fileDesc ,
chatName , std : : string ( sizeStr ) } ) ;
g_free ( sizeStr ) ;
InlineDownloadInfo * info = new InlineDownloadInfo ;
info - > fileId = file . id_ ;
info - > chatId = getId ( chat ) ;
info - > message = std : : move ( message ) ;
info - > fileDescription = fileDesc ;
info - > transceiver = & transceiver ;
info - > account = & account ;
// TRANSLATOR: Download dialog, title
purple_request_action ( purple_account_get_connection ( account . purpleAccount ) , _ ( " Download " ) , question . c_str ( ) ,
fileInfo . c_str ( ) , 0 , account . purpleAccount , NULL , NULL ,
// TRANSLATOR: Download dialog, alternative is "_No"
info , 2 , _ ( " _Yes " ) , startInlineDownload ,
// TRANSLATOR: Download dialog, alternative is "_Yes"
_ ( " _No " ) , ignoreInlineDownload ) ;
}
2020-10-12 22:41:13 +02:00
static void showFileInline ( const td : : td_api : : chat & chat , IncomingMessage & fullMessage ,
2020-10-12 20:23:29 +02:00
const td : : td_api : : file & file , const char * caption ,
const std : : string & fileDesc ,
TdTransceiver & transceiver , TdAccountData & account )
{
std : : string notice ;
bool askDownload = false ;
bool autoDownload = false ;
unsigned fileSize = getFileSizeKb ( file ) ;
if ( caption & & ( * caption = = ' \0 ' ) )
caption = NULL ;
if ( file . local_ & & file . local_ - > is_downloading_completed_ ) {
autoDownload = true ;
notice . clear ( ) ;
2020-10-12 22:41:13 +02:00
} else if ( isSizeWithinLimit ( fileSize , fullMessage . inlineFileSizeLimit ) ) {
2020-10-25 14:04:58 +01:00
// Sticker with a caption is not a thing but just in case it was: don't skip "Downloading..."
// notification for such hypothetical object because it will also include caption
2020-10-12 22:41:13 +02:00
if ( ! ( ( fullMessage . messageInfo . type = = TgMessageInfo : : Type : : Sticker ) & & ! caption ) & &
! fullMessage . inlineDownloadComplete )
{
2020-10-12 20:23:29 +02:00
// TRANSLATOR: In-chat notification, appears after a colon (':'). Argument is a file *type*, not a filename.
notice = formatMessage ( _ ( " Downloading {} " ) , std : : string ( fileDesc ) ) ;
}
autoDownload = true ;
} else if ( ! ignoreBigDownloads ( account . purpleAccount ) ) {
// TRANSLATOR: In-chat notification, appears after a colon (':'). Argument is a file *type*, not a filename.
notice = formatMessage ( _ ( " Requesting {} download " ) , std : : string ( fileDesc ) ) ;
askDownload = true ;
} else {
char * fileSizeStr = purple_str_size_to_units ( fileSize ) ; // File size above limit, so it's non-zero
// TRANSLATOR: In-chat notification, appears after a colon (':'). Arguments are a file *type*, not a filename; second argument is a file size with unit.
notice = formatMessage ( _ ( " Ignoring {0} download ({1}) " ) , { std : : string ( fileDesc ) , std : : string ( fileSizeStr ) } ) ;
g_free ( fileSizeStr ) ;
}
2020-10-25 14:04:58 +01:00
2020-10-12 20:23:29 +02:00
if ( ! notice . empty ( ) )
2020-10-12 22:41:13 +02:00
notice = makeNoticeWithSender ( chat , fullMessage . messageInfo , notice . c_str ( ) ,
account . purpleAccount ) ;
2020-10-12 20:23:29 +02:00
2020-10-25 14:04:58 +01:00
// Notice means file isn't downloaded yet or is ignored. Either way, show caption as well.
if ( ! notice . empty ( ) )
showMessageText ( account , chat , fullMessage . messageInfo , caption , notice . c_str ( ) ) ;
2020-10-12 20:23:29 +02:00
if ( autoDownload | | askDownload ) {
2020-10-18 18:33:41 +02:00
if ( fullMessage . animatedStickerConverted ) {
if ( fullMessage . animatedStickerConvertSuccess ) {
std : : string text = makeInlineImageText ( fullMessage . animatedStickerImageId ) ;
showMessageText ( account , chat , fullMessage . messageInfo , text . c_str ( ) , NULL , PURPLE_MESSAGE_IMAGES ) ;
}
} else if ( file . local_ & & file . local_ - > is_downloading_completed_ )
2020-10-12 22:41:13 +02:00
showDownloadedFileInline ( getId ( chat ) , fullMessage . messageInfo , file . local_ - > path_ ,
2020-10-25 16:20:13 +01:00
caption , fileDesc , std : : move ( fullMessage . thumbnail ) , transceiver , account ) ;
2020-10-12 22:41:13 +02:00
else if ( autoDownload & & fullMessage . inlineDownloadComplete )
showDownloadedFileInline ( getId ( chat ) , fullMessage . messageInfo , fullMessage . inlineDownloadedFilePath ,
2020-10-25 16:20:13 +01:00
caption , fileDesc , std : : move ( fullMessage . thumbnail ) , transceiver , account ) ;
2020-10-12 20:23:29 +02:00
else if ( autoDownload ) {
2020-10-12 22:41:13 +02:00
// When download takes too long, message will leave PendingMessageQueue and be "shown".
// However, nothing more should be done at that point except keep waiting for the download.
if ( ! fullMessage . inlineDownloadTimeout ) {
purple_debug_misc ( config : : pluginId , " Downloading %s (file id %d) \n " , fileDesc . c_str ( ) ,
( int ) file . id_ ) ;
downloadFileInline ( file . id_ , getId ( chat ) , fullMessage . messageInfo , fileDesc ,
2020-10-25 16:20:13 +01:00
std : : move ( fullMessage . thumbnail ) , transceiver , account ) ;
2020-10-12 22:41:13 +02:00
}
2020-10-12 20:23:29 +02:00
} else if ( askDownload ) {
2020-10-12 22:41:13 +02:00
std : : string sender = getSenderDisplayName ( chat , fullMessage . messageInfo ,
account . purpleAccount ) ;
requestInlineDownload ( sender . c_str ( ) , file , fileDesc , chat , fullMessage . messageInfo ,
transceiver , account ) ;
2020-10-12 20:23:29 +02:00
}
}
}
2020-10-12 22:41:13 +02:00
static void showPhotoMessage ( const td : : td_api : : chat & chat , IncomingMessage & fullMessage ,
2020-10-12 20:23:29 +02:00
const td : : td_api : : file * photoSize , const std : : string & caption ,
TdTransceiver & transceiver , TdAccountData & account )
{
const char * captionCstr = ! caption . empty ( ) ? caption . c_str ( ) : nullptr ;
if ( photoSize ) {
// TRANSLATOR: File-type, used to describe what is being downloaded, in sentences like "Downloading photo" or "Ignoring photo download".
2020-10-25 16:20:13 +01:00
showFileInline ( chat , fullMessage , * photoSize , captionCstr , _ ( " photo " ) ,
2020-10-12 22:41:13 +02:00
transceiver , account ) ;
2020-10-12 20:23:29 +02:00
} else {
// Unlikely message not worth translating
2020-10-12 22:41:13 +02:00
std : : string notice = makeNoticeWithSender ( chat , fullMessage . messageInfo , " Faulty image " ,
account . purpleAccount ) ;
showMessageText ( account , chat , fullMessage . messageInfo , captionCstr , notice . c_str ( ) ) ;
2020-10-12 20:23:29 +02:00
}
}
2020-10-12 22:41:13 +02:00
static void showFileMessage ( const td : : td_api : : chat & chat , IncomingMessage & fullMessage ,
2020-10-12 20:23:29 +02:00
const td : : td_api : : file * file ,
const std : : string & caption ,
const std : : string & fileDescription ,
const std : : string & fileName ,
TdTransceiver & transceiver , TdAccountData & account )
{
const char * captionStr = ! caption . empty ( ) ? caption . c_str ( ) : NULL ;
if ( ! file ) {
// Unlikely message not worth translating
std : : string notice = formatMessage ( " Faulty file: {} " , fileDescription ) ;
2020-10-12 22:41:13 +02:00
notice = makeNoticeWithSender ( chat , fullMessage . messageInfo , notice . c_str ( ) ,
account . purpleAccount ) ;
showMessageText ( account , chat , fullMessage . messageInfo , captionStr , notice . c_str ( ) ) ;
2020-10-12 20:23:29 +02:00
} else {
2020-10-12 22:41:13 +02:00
if ( ! fullMessage . standardDownloadConfigured | | ! chat . type_ | |
2020-10-12 20:23:29 +02:00
( ( chat . type_ - > get_id ( ) ! = td : : td_api : : chatTypePrivate : : ID ) & &
( chat . type_ - > get_id ( ) ! = td : : td_api : : chatTypeSecret : : ID ) ) )
{
2020-10-25 16:20:13 +01:00
showFileInline ( chat , fullMessage , * file , captionStr , fileDescription ,
2020-10-12 22:41:13 +02:00
transceiver , account ) ;
2020-10-12 20:23:29 +02:00
} else
2020-10-12 22:41:13 +02:00
requestStandardDownload ( getId ( chat ) , fullMessage . messageInfo , fileName , * file ,
transceiver , account ) ;
2020-10-12 20:23:29 +02:00
}
}
2020-10-12 22:41:13 +02:00
static void showStickerMessage ( const td : : td_api : : chat & chat , IncomingMessage & fullMessage ,
2020-10-12 20:23:29 +02:00
td : : td_api : : messageSticker & stickerContent ,
TdTransceiver & transceiver , TdAccountData & account )
{
if ( ! stickerContent . sticker_ ) return ;
td : : td_api : : sticker & sticker = * stickerContent . sticker_ ;
2020-10-25 16:20:13 +01:00
if ( sticker . sticker_ )
2020-10-12 20:23:29 +02:00
// TRANSLATOR: File-type, used to describe what is being downloaded, in sentences like "Downloading photo" or "Ignoring photo download".
2020-10-25 16:20:13 +01:00
showFileInline ( chat , fullMessage , * sticker . sticker_ , NULL , _ ( " sticker " ) ,
2020-10-12 20:23:29 +02:00
transceiver , account ) ;
}
void showMessage ( const td : : td_api : : chat & chat , IncomingMessage & fullMessage ,
TdTransceiver & transceiver , TdAccountData & account )
{
if ( ! fullMessage . message ) return ;
td : : td_api : : message & message = * fullMessage . message ;
if ( ! message . content_ )
return ;
purple_debug_misc ( config : : pluginId , " Displaying message % " G_GINT64_FORMAT " \n " , message . id_ ) ;
TgMessageInfo & messageInfo = fullMessage . messageInfo ;
messageInfo . repliedMessage = std : : move ( fullMessage . repliedMessage ) ;
if ( message . ttl_ ! = 0 ) {
// TRANSLATOR: In-chat warning message
const char * text = _ ( " Received self-destructing message, not displayed due to lack of support " ) ;
std : : string notice = makeNoticeWithSender ( chat , messageInfo , text , account . purpleAccount ) ;
showMessageText ( account , chat , messageInfo , NULL , notice . c_str ( ) ) ;
return ;
}
FileInfo fileInfo ;
getFileFromMessage ( fullMessage , fileInfo ) ;
if ( fileInfo . secret ) {
// TRANSLATOR: In-chat warning message
std : : string notice = formatMessage ( " Ignoring secret file ({}) " , fileInfo . description ) ;
notice = makeNoticeWithSender ( chat , messageInfo , notice . c_str ( ) , account . purpleAccount ) ;
showMessageText ( account , chat , messageInfo , ! fileInfo . caption . empty ( ) ? fileInfo . caption . c_str ( ) : nullptr ,
notice . c_str ( ) ) ;
return ;
}
switch ( message . content_ - > get_id ( ) ) {
case td : : td_api : : messageText : : ID :
showTextMessage ( chat , messageInfo , static_cast < const td : : td_api : : messageText & > ( * message . content_ ) ,
account ) ;
break ;
case td : : td_api : : messagePhoto : : ID :
2020-10-12 22:41:13 +02:00
showPhotoMessage ( chat , fullMessage , fileInfo . file , fileInfo . caption , transceiver , account ) ;
2020-10-12 20:23:29 +02:00
break ;
case td : : td_api : : messageDocument : : ID :
case td : : td_api : : messageVideo : : ID :
case td : : td_api : : messageAnimation : : ID :
case td : : td_api : : messageAudio : : ID :
case td : : td_api : : messageVoiceNote : : ID :
case td : : td_api : : messageVideoNote : : ID :
2020-10-12 22:41:13 +02:00
showFileMessage ( chat , fullMessage , fileInfo . file , fileInfo . caption , fileInfo . description ,
2020-10-12 20:23:29 +02:00
fileInfo . name , transceiver , account ) ;
break ;
case td : : td_api : : messageSticker : : ID :
2020-10-12 22:41:13 +02:00
showStickerMessage ( chat , fullMessage , static_cast < td : : td_api : : messageSticker & > ( * message . content_ ) ,
2020-10-12 20:23:29 +02:00
transceiver , account ) ;
break ;
case td : : td_api : : messageChatChangeTitle : : ID : {
const auto & titleChange = static_cast < const td : : td_api : : messageChatChangeTitle & > ( * message . content_ ) ;
// TRANSLATOR: In-chat status update, arguments are chat names.
std : : string notice = formatMessage ( _ ( " {0} changed group name to {1} " ) ,
{ getSenderDisplayName ( chat , messageInfo , account . purpleAccount ) ,
titleChange . title_ } ) ;
showMessageText ( account , chat , messageInfo , NULL , notice . c_str ( ) ) ;
break ;
}
case td : : td_api : : messageCall : : ID :
showCallMessage ( chat , messageInfo , static_cast < td : : td_api : : messageCall & > ( * message . content_ ) , account ) ;
break ;
default : {
// TRANSLATOR: In-chat error message, argument will be a Telegram type.
std : : string notice = getUnsupportedMessageDescription ( * message . content_ ) ;
notice = makeNoticeWithSender ( chat , messageInfo , notice . c_str ( ) , account . purpleAccount ) ;
showMessageText ( account , chat , messageInfo , NULL , notice . c_str ( ) ) ;
}
}
2020-10-25 14:57:45 +01:00
// Put it back - may be needed if long download is in progress
fullMessage . repliedMessage = std : : move ( messageInfo . repliedMessage ) ;
2020-10-12 20:23:29 +02:00
}
2021-01-02 22:57:20 +01:00
void showMessages ( std : : vector < IncomingMessage > & messages , TdAccountData & account )
2020-10-12 20:23:29 +02:00
{
for ( IncomingMessage & readyMessage : messages ) {
if ( ! readyMessage . message ) continue ;
const td : : td_api : : chat * chat = account . getChat ( getChatId ( * readyMessage . message ) ) ;
if ( chat )
2021-01-02 22:57:20 +01:00
showMessage ( * chat , readyMessage , account . transceiver , account ) ;
2020-10-12 20:23:29 +02:00
}
}
2020-10-11 11:48:51 +02:00
const td : : td_api : : file * selectPhotoSize ( PurpleAccount * account , const td : : td_api : : messagePhoto & photo )
{
unsigned sizeLimit = getAutoDownloadLimitKb ( account ) ;
const td : : td_api : : photoSize * selectedSize = nullptr ;
bool selectedFileSize = 0 ;
if ( photo . photo_ )
for ( const auto & newSize : photo . photo_ - > sizes_ )
if ( newSize & & newSize - > photo_ ) {
unsigned fileSize = getFileSizeKb ( * newSize - > photo_ ) ;
bool isWithinLimit = isSizeWithinLimit ( fileSize , sizeLimit ) ;
bool selectedWithinLimit = isSizeWithinLimit ( selectedFileSize , sizeLimit ) ;
if ( ! selectedSize | |
( ! selectedWithinLimit & & ( isWithinLimit | | ( fileSize < selectedFileSize ) ) ) | |
( selectedWithinLimit & & isWithinLimit & & ( newSize - > width_ > selectedSize - > width_ ) ) )
{
selectedSize = newSize . get ( ) ;
selectedFileSize = fileSize ;
}
}
if ( selectedSize )
purple_debug_misc ( config : : pluginId , " Selected size %dx%d for photo \n " ,
( int ) selectedSize - > width_ , ( int ) selectedSize - > height_ ) ;
else
purple_debug_warning ( config : : pluginId , " No file found for a photo \n " ) ;
return selectedSize ? selectedSize - > photo_ . get ( ) : nullptr ;
}
2020-10-11 19:41:18 +02:00
void makeFullMessage ( const td : : td_api : : chat & chat , td : : td_api : : object_ptr < td : : td_api : : message > message ,
IncomingMessage & fullMessage , const TdAccountData & account )
2020-10-11 12:54:20 +02:00
{
2020-10-11 19:41:18 +02:00
if ( ! message ) {
fullMessage . message = nullptr ;
return ;
}
2020-10-11 12:54:20 +02:00
fullMessage . repliedMessage = nullptr ;
fullMessage . selectedPhotoSizeId = 0 ;
2020-10-25 14:57:45 +01:00
fullMessage . repliedMessageFetchDoneOrFailed = false ;
2020-10-12 22:41:13 +02:00
fullMessage . inlineDownloadComplete = false ;
fullMessage . inlineDownloadTimeout = false ;
2020-10-18 18:33:41 +02:00
fullMessage . animatedStickerConverted = false ;
fullMessage . animatedStickerConvertSuccess = false ;
fullMessage . animatedStickerImageId = 0 ;
2020-10-11 12:54:20 +02:00
const char * option = purple_account_get_string ( account . purpleAccount , AccountOptions : : DownloadBehaviour ,
AccountOptions : : DownloadBehaviourDefault ( ) ) ;
2020-10-11 14:38:37 +02:00
fullMessage . standardDownloadConfigured = ( strcmp ( option , AccountOptions : : DownloadBehaviourHyperlink ) ! = 0 ) ;
2020-10-11 12:54:20 +02:00
fullMessage . inlineFileSizeLimit = getAutoDownloadLimitKb ( account . purpleAccount ) ;
2020-10-11 19:41:18 +02:00
TgMessageInfo & messageInfo = fullMessage . messageInfo ;
2020-10-12 22:41:13 +02:00
messageInfo . id = getId ( * message ) ;
2020-10-11 19:41:18 +02:00
messageInfo . type = TgMessageInfo : : Type : : Other ;
messageInfo . incomingGroupchatSender = getIncomingGroupchatSenderPurpleName ( chat , * message , account ) ;
messageInfo . timestamp = message - > date_ ;
messageInfo . outgoing = message - > is_outgoing_ ;
messageInfo . sentLocally = ( message - > sending_state_ ! = nullptr ) ;
messageInfo . repliedMessageId = getReplyMessageId ( * message ) ;
if ( message - > forward_info_ )
messageInfo . forwardedFrom = getForwardSource ( * message - > forward_info_ , account ) ;
2020-10-11 12:54:20 +02:00
if ( message & & message - > content_ ) {
if ( message - > content_ - > get_id ( ) = = td : : td_api : : messagePhoto : : ID ) {
2020-10-12 18:15:24 +02:00
messageInfo . type = TgMessageInfo : : Type : : Photo ;
2020-10-11 12:54:20 +02:00
const td : : td_api : : messagePhoto & photo = static_cast < const td : : td_api : : messagePhoto & > ( * message - > content_ ) ;
const td : : td_api : : file * file = selectPhotoSize ( account . purpleAccount , photo ) ;
if ( file )
fullMessage . selectedPhotoSizeId = file - > id_ ;
2020-10-25 16:20:13 +01:00
} else if ( message - > content_ - > get_id ( ) = = td : : td_api : : messageSticker : : ID ) {
2020-10-11 19:41:18 +02:00
messageInfo . type = TgMessageInfo : : Type : : Sticker ;
2020-10-25 16:20:13 +01:00
td : : td_api : : messageSticker & sticker = static_cast < td : : td_api : : messageSticker & > ( * message - > content_ ) ;
if ( sticker . sticker_ & & sticker . sticker_ - > thumbnail_ ) {
fullMessage . thumbnail = std : : move ( sticker . sticker_ - > thumbnail_ - > photo_ ) ;
}
}
2020-10-11 12:54:20 +02:00
}
fullMessage . message = std : : move ( message ) ;
}
static const td : : td_api : : file * getSelectedPhotoSize ( const IncomingMessage & fullMessage ,
const td : : td_api : : messagePhoto & photo )
{
if ( photo . photo_ )
for ( const auto & newSize : photo . photo_ - > sizes_ )
if ( newSize & & newSize - > photo_ & & ( newSize - > photo_ - > id_ = = fullMessage . selectedPhotoSizeId ) )
return newSize - > photo_ . get ( ) ;
return nullptr ;
}
2020-10-11 14:38:37 +02:00
static bool isInlineDownload ( const IncomingMessage & fullMessage ,
const td : : td_api : : MessageContent & content ,
const td : : td_api : : chat & chat )
{
return ( content . get_id ( ) = = td : : td_api : : messagePhoto : : ID ) | |
( content . get_id ( ) = = td : : td_api : : messageSticker : : ID ) | |
! fullMessage . standardDownloadConfigured | | ! chat . type_ | |
( ( chat . type_ - > get_id ( ) ! = td : : td_api : : chatTypePrivate : : ID ) & &
( chat . type_ - > get_id ( ) ! = td : : td_api : : chatTypeSecret : : ID ) ) ;
}
static bool inlineDownloadNeedAutoDl ( const IncomingMessage & fullMessage ,
const td : : td_api : : file & file )
{
unsigned fileSize = getFileSizeKb ( file ) ;
return ! ( ( file . local_ & & file . local_ - > is_downloading_completed_ ) ) & &
isSizeWithinLimit ( fileSize , fullMessage . inlineFileSizeLimit ) ;
}
2020-10-11 12:54:20 +02:00
static bool isFileMessageReady ( const IncomingMessage & fullMessage , ChatId chatId ,
const td : : td_api : : MessageContent & content ,
2020-10-11 14:38:37 +02:00
const td : : td_api : : file & file , const TdAccountData & account )
2020-10-11 12:54:20 +02:00
{
const td : : td_api : : chat * chat = account . getChat ( chatId ) ;
2020-10-12 22:41:13 +02:00
if ( chat & & isInlineDownload ( fullMessage , content , * chat ) ) {
2020-10-11 13:55:44 +02:00
// File will be shown inline
2020-10-18 18:33:41 +02:00
// Animated stickers are not ready until converted
if ( fullMessage . inlineDownloadComplete )
return ! ( ( content . get_id ( ) = = td : : td_api : : messageSticker : : ID ) & &
isStickerAnimated ( fullMessage . inlineDownloadedFilePath ) & &
shouldConvertAnimatedSticker ( fullMessage . messageInfo , account . purpleAccount ) & &
! fullMessage . animatedStickerConverted ) ;
else if ( file . local_ & & file . local_ - > is_downloading_completed_ )
return ! ( ( content . get_id ( ) = = td : : td_api : : messageSticker : : ID ) & &
isStickerAnimated ( file . local_ - > path_ ) & &
shouldConvertAnimatedSticker ( fullMessage . messageInfo , account . purpleAccount ) & &
! fullMessage . animatedStickerConverted ) ;
else
// Files above limit will either be ignored (in which case, message is ready)
// or requested (in which case, don't try do display in order)
return fullMessage . inlineDownloadTimeout | | ! inlineDownloadNeedAutoDl ( fullMessage , file ) ;
2020-10-11 12:54:20 +02:00
} else
// Standard libpurple transfer will be used, nothing to postpone
return true ;
}
2020-10-11 21:35:10 +02:00
void getFileFromMessage ( const IncomingMessage & fullMessage , FileInfo & result )
2020-10-11 11:48:51 +02:00
{
2020-10-11 21:35:10 +02:00
result . file = nullptr ;
result . caption = " " ;
result . secret = false ;
2020-10-11 14:38:37 +02:00
if ( ! fullMessage . message | | ! fullMessage . message - > content_ )
2020-10-11 21:35:10 +02:00
return ;
2020-10-11 11:48:51 +02:00
const td : : td_api : : message & message = * fullMessage . message ;
2020-10-11 12:54:20 +02:00
2020-10-11 11:48:51 +02:00
switch ( message . content_ - > get_id ( ) ) {
case td : : td_api : : messagePhoto : : ID : {
const td : : td_api : : messagePhoto & photo = static_cast < const td : : td_api : : messagePhoto & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = getSelectedPhotoSize ( fullMessage , photo ) ;
result . name = " " ; // will not be needed - inline download only
if ( photo . caption_ ) result . caption = photo . caption_ - > text_ ;
// TRANSLATOR: File-type, used to describe what is being downloaded, in sentences like "Downloading photo" or "Ignoring photo download".
result . description = _ ( " photo " ) ;
result . secret = photo . is_secret_ ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageDocument : : ID : {
const td : : td_api : : messageDocument & document = static_cast < const td : : td_api : : messageDocument & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = document . document_ ? document . document_ - > document_ . get ( ) : nullptr ;
if ( document . caption_ ) result . caption = document . caption_ - > text_ ;
result . name = getFileName ( document . document_ . get ( ) ) ;
result . description = makeDocumentDescription ( document . document_ . get ( ) ) ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageVideo : : ID : {
const td : : td_api : : messageVideo & video = static_cast < const td : : td_api : : messageVideo & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = video . video_ ? video . video_ - > video_ . get ( ) : nullptr ;
if ( video . caption_ ) result . caption = video . caption_ - > text_ ;
result . name = getFileName ( video . video_ . get ( ) ) ;
result . description = makeDocumentDescription ( video . video_ . get ( ) ) ;
result . secret = video . is_secret_ ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageAnimation : : ID : {
const td : : td_api : : messageAnimation & animation = static_cast < const td : : td_api : : messageAnimation & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = animation . animation_ ? animation . animation_ - > animation_ . get ( ) : nullptr ;
if ( animation . caption_ ) result . caption = animation . caption_ - > text_ ;
result . name = getFileName ( animation . animation_ . get ( ) ) ;
result . description = makeDocumentDescription ( animation . animation_ . get ( ) ) ;
result . secret = animation . is_secret_ ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageAudio : : ID : {
const td : : td_api : : messageAudio & audio = static_cast < const td : : td_api : : messageAudio & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = audio . audio_ ? audio . audio_ - > audio_ . get ( ) : nullptr ;
if ( audio . caption_ ) result . caption = audio . caption_ - > text_ ;
result . name = getFileName ( audio . audio_ . get ( ) ) ;
result . description = makeDocumentDescription ( audio . audio_ . get ( ) ) ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageVoiceNote : : ID : {
const td : : td_api : : messageVoiceNote & audio = static_cast < const td : : td_api : : messageVoiceNote & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = audio . voice_note_ ? audio . voice_note_ - > voice_ . get ( ) : nullptr ;
if ( audio . caption_ ) result . caption = audio . caption_ - > text_ ;
result . name = getFileName ( audio . voice_note_ . get ( ) ) ;
result . description = makeDocumentDescription ( audio . voice_note_ . get ( ) ) ;
break ;
2020-10-11 11:48:51 +02:00
}
case td : : td_api : : messageVideoNote : : ID : {
const td : : td_api : : messageVideoNote & video = static_cast < const td : : td_api : : messageVideoNote & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = video . video_note_ ? video . video_note_ - > video_ . get ( ) : nullptr ;
result . name = getFileName ( video . video_note_ . get ( ) ) ;
result . description = makeDocumentDescription ( video . video_note_ . get ( ) ) ;
result . secret = video . is_secret_ ;
2020-10-11 11:48:51 +02:00
break ;
}
2020-10-11 12:54:20 +02:00
case td : : td_api : : messageSticker : : ID : {
const td : : td_api : : messageSticker & sticker = static_cast < const td : : td_api : : messageSticker & > ( * message . content_ ) ;
2020-10-11 21:35:10 +02:00
result . file = sticker . sticker_ ? sticker . sticker_ - > sticker_ . get ( ) : nullptr ;
result . name = " " ; // will not be needed - inline download only
// TRANSLATOR: File-type, used to describe what is being downloaded, in sentences like "Downloading photo" or "Ignoring photo download".
result . description = _ ( " sticker " ) ;
break ;
2020-10-11 12:54:20 +02:00
}
2020-10-11 11:48:51 +02:00
}
2020-10-11 14:38:37 +02:00
}
bool isMessageReady ( const IncomingMessage & fullMessage , const TdAccountData & account )
{
if ( ! fullMessage . message ) return true ;
const td : : td_api : : message & message = * fullMessage . message ;
ChatId chatId = getChatId ( message ) ;
2020-10-25 14:57:45 +01:00
if ( getReplyMessageId ( message ) . valid ( ) & & ! fullMessage . repliedMessageFetchDoneOrFailed )
2020-10-11 14:38:37 +02:00
{
return false ;
}
2020-10-12 22:41:13 +02:00
if ( message . content_ )
{
FileInfo fileInfo ;
getFileFromMessage ( fullMessage , fileInfo ) ;
// For stickers, will wait for sticker download to display the message, but not for
// animated sticker conversion
if ( fileInfo . file & & ! isFileMessageReady ( fullMessage , chatId , * message . content_ ,
* fileInfo . file , account ) )
{
return false ;
}
}
2020-10-11 14:38:37 +02:00
2020-10-11 11:48:51 +02:00
return true ;
}
2020-10-11 21:35:10 +02:00
void fetchExtras ( IncomingMessage & fullMessage , TdTransceiver & transceiver , TdAccountData & account ,
2020-10-11 11:48:51 +02:00
TdTransceiver : : ResponseCb2 onFetchReply )
{
2020-10-11 14:38:37 +02:00
if ( ! fullMessage . message ) return ;
const td : : td_api : : message & message = * fullMessage . message ;
MessageId messageId = getId ( message ) ;
MessageId replyMessageId = getReplyMessageId ( message ) ;
ChatId chatId = getChatId ( message ) ;
const td : : td_api : : chat * chat = account . getChat ( chatId ) ;
2020-10-11 11:48:51 +02:00
if ( replyMessageId . valid ( ) ) {
purple_debug_misc ( config : : pluginId , " Fetching message % " G_GINT64_FORMAT " which message % " G_GINT64_FORMAT " replies to \n " ,
replyMessageId . value ( ) , messageId . value ( ) ) ;
auto getMessageReq = td : : td_api : : make_object < td : : td_api : : getMessage > ( ) ;
2020-10-11 14:38:37 +02:00
getMessageReq - > chat_id_ = chatId . value ( ) ;
2020-10-11 11:48:51 +02:00
getMessageReq - > message_id_ = replyMessageId . value ( ) ;
transceiver . sendQueryWithTimeout ( std : : move ( getMessageReq ) , onFetchReply , 1 ) ;
}
2020-10-11 14:38:37 +02:00
2020-10-11 21:35:10 +02:00
FileInfo fileInfo ;
getFileFromMessage ( fullMessage , fileInfo ) ;
2020-10-18 18:33:41 +02:00
if ( fileInfo . file & & message . content_ & & chat & & isInlineDownload ( fullMessage , * message . content_ , * chat ) ) {
if ( fileInfo . file - > local_ & & fileInfo . file - > local_ - > is_downloading_completed_ & &
( message . content_ - > get_id ( ) = = td : : td_api : : messageSticker : : ID ) & &
isStickerAnimated ( fileInfo . file - > local_ - > path_ ) )
{
if ( shouldConvertAnimatedSticker ( fullMessage . messageInfo , account . purpleAccount ) ) {
StickerConversionThread * thread ;
thread = new StickerConversionThread ( account . purpleAccount , fileInfo . file - > local_ - > path_ ,
chatId , & fullMessage . messageInfo ) ;
thread - > startThread ( ) ;
}
2020-10-25 16:20:13 +01:00
// TODO: if animated stickers are disabled, fetch thumbnail instead
2020-10-18 18:33:41 +02:00
} else if ( inlineDownloadNeedAutoDl ( fullMessage , * fileInfo . file ) ) {
// TgMessageInfo on fullMessage has replyMessage=NULL which will be copied onto DownloadRequest.
// If message leaves PendingMessageQueue while download is still active, there's probably
// a replyMessage on IncomingMessage by then, and it needs to be moved over to DownloadRequest.
2020-10-25 16:20:13 +01:00
// Thumbnail also needs moving onto DownloadRequest.
2020-10-18 18:33:41 +02:00
downloadFileInline ( fileInfo . file - > id_ , chatId , fullMessage . messageInfo , fileInfo . description ,
nullptr , transceiver , account ) ;
}
2020-10-11 14:38:37 +02:00
}
2020-10-11 11:48:51 +02:00
}
2020-10-12 20:23:29 +02:00
void checkMessageReady ( const IncomingMessage * message , TdTransceiver & transceiver ,
2020-10-25 14:57:45 +01:00
TdAccountData & account , std : : vector < IncomingMessage > * rvReadyMessages )
2020-10-12 20:23:29 +02:00
{
if ( ! message | | ! message - > message ) return ;
if ( isMessageReady ( * message , account ) ) {
std : : vector < IncomingMessage > readyMessages ;
account . pendingMessages . setMessageReady ( getChatId ( * message - > message ) , getId ( * message - > message ) ,
2020-10-25 14:57:45 +01:00
rvReadyMessages ? * rvReadyMessages : readyMessages ) ;
2020-10-12 20:23:29 +02:00
message = nullptr ;
2021-01-02 22:57:20 +01:00
showMessages ( rvReadyMessages ? * rvReadyMessages : readyMessages , account ) ;
2020-10-12 20:23:29 +02:00
}
}
2021-01-02 22:57:20 +01:00
2021-01-03 01:54:17 +01:00
static void findMessageResponse ( TdAccountData & account , ChatId chatId , MessageId pendingMessageId ,
td : : td_api : : object_ptr < td : : td_api : : Object > object )
{
IncomingMessage * pendingMessage = account . pendingMessages . findPendingMessage ( chatId , pendingMessageId ) ;
if ( ! pendingMessage ) return ;
pendingMessage - > repliedMessageFetchDoneOrFailed = true ;
if ( object & & ( object - > get_id ( ) = = td : : td_api : : message : : ID ) )
pendingMessage - > repliedMessage = td : : move_tl_object_as < td : : td_api : : message > ( object ) ;
else
purple_debug_misc ( config : : pluginId , " Failed to fetch reply source for message % " G_GINT64_FORMAT " \n " ,
pendingMessageId . value ( ) ) ;
checkMessageReady ( pendingMessage , account . transceiver , account ) ;
}
void handleIncomingMessage ( TdAccountData & account , const td : : td_api : : chat & chat ,
td : : td_api : : object_ptr < td : : td_api : : message > message ,
PendingMessageQueue : : MessageAction action )
{
if ( ! message ) return ;
ChatId chatId = getId ( chat ) ;
if ( isReadReceiptsEnabled ( account . purpleAccount ) )
account . addPendingReadReceipt ( chatId , getId ( * message ) ) ;
IncomingMessage fullMessage ;
makeFullMessage ( chat , std : : move ( message ) , fullMessage , account ) ;
if ( isMessageReady ( fullMessage , account ) ) {
2021-01-03 02:01:05 +01:00
IncomingMessage readyMessage = account . pendingMessages . addReadyMessage ( std : : move ( fullMessage ) , action ) ;
2021-01-03 01:54:17 +01:00
if ( readyMessage . message )
showMessage ( chat , readyMessage , account . transceiver , account ) ;
} else {
MessageId messageId = getId ( * fullMessage . message ) ;
2021-01-03 02:01:05 +01:00
IncomingMessage & addedMessage = account . pendingMessages . addPendingMessage ( std : : move ( fullMessage ) , action ) ;
2021-01-03 01:54:17 +01:00
fetchExtras ( addedMessage , account . transceiver , account ,
[ & account , chatId , messageId ] ( uint64_t , td : : td_api : : object_ptr < td : : td_api : : Object > object ) {
findMessageResponse ( account , chatId , messageId , std : : move ( object ) ) ;
}
) ;
}
}
static void fetchHistoryRequest ( TdAccountData & account , ChatId chatId , unsigned messagesFetched ,
2021-01-02 22:57:20 +01:00
MessageId fetchBackFrom , MessageId lastReceivedMessage ) ;
2021-01-03 14:20:23 +01:00
static void fetchHistoryResponse ( TdAccountData & account , ChatId chatId , MessageId stopAt ,
2021-01-03 01:54:17 +01:00
unsigned messagesFetched ,
2021-01-02 22:57:20 +01:00
td : : td_api : : object_ptr < td : : td_api : : Object > response )
{
MessageId requestMoreFrom = MessageId : : invalid ;
2021-01-03 13:18:33 +01:00
const td : : td_api : : chat * chat = account . getChat ( chatId ) ;
2021-01-02 22:57:20 +01:00
if ( response & & ( response - > get_id ( ) = = td : : td_api : : messages : : ID ) ) {
td : : td_api : : messages & messages = static_cast < td : : td_api : : messages & > ( * response ) ;
2021-01-03 13:18:33 +01:00
purple_debug_misc ( config : : pluginId , " Fetched %zu messages for chat % " G_GINT64_FORMAT " \n " ,
messages . messages_ . size ( ) , chatId . value ( ) ) ;
auto stop = messages . messages_ . begin ( ) ;
MessageId lastMessageId = MessageId : : invalid ;
for ( ; stop ! = messages . messages_ . end ( ) ; + + stop ) {
td : : td_api : : object_ptr < td : : td_api : : message > message = std : : move ( * stop ) ;
if ( ! message ) {
purple_debug_warning ( config : : pluginId , " Erroneous message in history, stopping \n " ) ;
break ;
2021-01-03 01:54:17 +01:00
}
2021-01-03 14:20:23 +01:00
if ( stopAt . valid ( ) & & ( getId ( * message ) = = stopAt ) ) {
2021-01-03 13:18:33 +01:00
purple_debug_misc ( config : : pluginId , " Found message % " G_GINT64_FORMAT " , stopping \n " ,
2021-01-03 14:20:23 +01:00
stopAt . value ( ) ) ;
2021-01-03 13:18:33 +01:00
break ;
}
2021-01-03 14:20:23 +01:00
if ( ( ! stopAt . valid ( ) & & ( messagesFetched = = 100 ) ) | |
2021-01-03 13:18:33 +01:00
( messagesFetched = = HISTORY_MESSAGES_ABSOLUTE_LIMIT ) )
{
purple_debug_misc ( config : : pluginId , " Reached history limit, stopping \n " ) ;
break ;
}
messagesFetched + + ;
lastMessageId = getId ( * message ) ;
if ( chat )
handleIncomingMessage ( account , * chat , std : : move ( message ) , PendingMessageQueue : : Prepend ) ;
2021-01-02 22:57:20 +01:00
}
2021-01-03 13:18:33 +01:00
if ( stop = = messages . messages_ . end ( ) )
requestMoreFrom = lastMessageId ;
2021-01-02 22:57:20 +01:00
} else {
std : : string message = formatMessage ( _ ( " Failed to fetch earlier messages: {} " ) ,
getDisplayedError ( response ) ) ;
purple_debug_warning ( config : : pluginId , " %s \n " , message . c_str ( ) ) ;
if ( chat )
showChatNotification ( account , * chat , message . c_str ( ) , PURPLE_MESSAGE_ERROR ) ;
}
if ( requestMoreFrom . valid ( ) )
2021-01-03 14:20:23 +01:00
fetchHistoryRequest ( account , chatId , messagesFetched , requestMoreFrom , stopAt ) ;
2021-01-02 22:57:20 +01:00
else {
2021-01-03 13:18:33 +01:00
purple_debug_misc ( config : : pluginId , " Done fetching history for chat % " G_GINT64_FORMAT " \n " ,
chatId . value ( ) ) ;
2021-01-02 22:57:20 +01:00
std : : vector < IncomingMessage > readyMessages ;
account . pendingMessages . setChatReady ( chatId , readyMessages ) ;
showMessages ( readyMessages , account ) ;
}
}
2021-01-03 01:54:17 +01:00
static void fetchHistoryRequest ( TdAccountData & account , ChatId chatId , unsigned messagesFetched ,
2021-01-03 14:20:23 +01:00
MessageId fetchBackFrom , MessageId stopAt )
2021-01-02 22:57:20 +01:00
{
auto request = td : : td_api : : make_object < td : : td_api : : getChatHistory > ( ) ;
request - > chat_id_ = chatId . value ( ) ;
request - > from_message_id_ = fetchBackFrom . valid ( ) ? fetchBackFrom . value ( ) : 0 ;
request - > limit_ = 30 ;
request - > offset_ = 0 ;
request - > only_local_ = false ;
2021-01-03 13:18:33 +01:00
purple_debug_misc ( config : : pluginId , " Requesting history for chat % " G_GINT64_FORMAT
" starting from % " G_GINT64_FORMAT " \n " , chatId . value ( ) , fetchBackFrom . value ( ) ) ;
2021-01-02 22:57:20 +01:00
account . transceiver . sendQuery ( std : : move ( request ) ,
2021-01-03 14:20:23 +01:00
[ & account , chatId , messagesFetched , stopAt ] ( uint64_t requestId , td : : td_api : : object_ptr < td : : td_api : : Object > response ) {
fetchHistoryResponse ( account , chatId , stopAt , messagesFetched , std : : move ( response ) ) ;
2021-01-02 22:57:20 +01:00
} ) ;
}
2021-01-03 14:20:23 +01:00
void fetchHistory ( TdAccountData & account , ChatId chatId , MessageId fetchFrom , MessageId stopAt )
2021-01-02 22:57:20 +01:00
{
if ( ! account . pendingMessages . isChatReady ( chatId ) )
return ;
account . pendingMessages . setChatNotReady ( chatId ) ;
2021-01-03 14:20:23 +01:00
fetchHistoryRequest ( account , chatId , 0 , fetchFrom , stopAt ) ;
2021-01-02 22:57:20 +01:00
}