2020-05-15 19:46:07 +02:00
# include "client-utils.h"
2020-05-25 23:21:04 +02:00
# include "purple-info.h"
2020-05-11 20:18:25 +02:00
# include "config.h"
2020-05-16 13:38:00 +02:00
# include "format.h"
2020-07-26 14:51:51 +02:00
# include "file-transfer.h"
2020-05-16 00:16:06 +02:00
# include <string.h>
# include <stdlib.h>
2020-06-06 11:16:43 +02:00
# include <algorithm>
2020-06-13 16:01:05 +02:00
# include <functional>
2020-05-29 12:16:03 +02:00
2020-05-28 21:29:58 +02:00
enum {
2020-07-26 14:51:51 +02:00
MAX_MESSAGE_PARTS = 10 ,
2020-05-28 21:29:58 +02:00
} ;
2020-06-05 20:14:17 +02:00
const char * errorCodeMessage ( )
{
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-line error message, appears after a colon (':'), arguments will be a number and some error text from Telegram
2020-08-20 19:13:30 +02:00
return _ ( " code {0} ({1}) " ) ;
2020-06-05 20:14:17 +02:00
}
2020-09-02 21:20:53 +02:00
static std : : string messageTypeToString ( const td : : td_api : : MessageContent & content )
2020-05-11 20:18:25 +02:00
{
# define C(type) case td::td_api::type::ID: return #type;
switch ( content . get_id ( ) ) {
C ( messageText )
C ( messageAnimation )
C ( messageAudio )
C ( messageDocument )
C ( messagePhoto )
C ( messageExpiredPhoto )
C ( messageSticker )
C ( messageVideo )
C ( messageExpiredVideo )
C ( messageVideoNote )
C ( messageVoiceNote )
C ( messageLocation )
C ( messageVenue )
C ( messageContact )
C ( messageGame )
C ( messagePoll )
C ( messageInvoice )
C ( messageCall )
C ( messageBasicGroupChatCreate )
C ( messageSupergroupChatCreate )
C ( messageChatChangeTitle )
C ( messageChatChangePhoto )
C ( messageChatDeletePhoto )
C ( messageChatAddMembers )
C ( messageChatJoinByLink )
C ( messageChatDeleteMember )
C ( messageChatUpgradeTo )
C ( messageChatUpgradeFrom )
C ( messagePinMessage )
C ( messageScreenshotTaken )
C ( messageChatSetTtl )
C ( messageCustomServiceAction )
C ( messageGameScore )
C ( messagePaymentSuccessful )
C ( messagePaymentSuccessfulBot )
C ( messageContactRegistered )
C ( messageWebsiteConnected )
C ( messagePassportDataSent )
C ( messagePassportDataReceived )
C ( messageUnsupported )
}
# undef C
return " id " + std : : to_string ( content . get_id ( ) ) ;
}
2020-09-02 21:20:53 +02:00
std : : string getUnsupportedMessageDescription ( const td : : td_api : : MessageContent & content )
{
return formatMessage ( _ ( " Unsupported message type {} " ) , messageTypeToString ( content ) ) ;
}
2020-05-20 18:31:37 +02:00
std : : string proxyTypeToString ( PurpleProxyType proxyType )
{
switch ( proxyType ) {
case PURPLE_PROXY_NONE :
case PURPLE_PROXY_USE_GLOBAL :
case PURPLE_PROXY_USE_ENVVAR :
return " unknown " ;
case PURPLE_PROXY_HTTP :
return " HTTP " ;
case PURPLE_PROXY_SOCKS4 :
return " SOCKS4 " ;
case PURPLE_PROXY_SOCKS5 :
return " SOCKS5 " ;
case PURPLE_PROXY_TOR :
return " TOR " ;
}
return " unknown " ;
}
2020-05-11 20:18:25 +02:00
const char * getPurpleStatusId ( const td : : td_api : : UserStatus & tdStatus )
{
if ( tdStatus . get_id ( ) = = td : : td_api : : userStatusOnline : : ID )
return purple_primitive_get_id_from_type ( PURPLE_STATUS_AVAILABLE ) ;
else
return purple_primitive_get_id_from_type ( PURPLE_STATUS_OFFLINE ) ;
}
2020-05-16 20:38:58 +02:00
std : : string getPurpleBuddyName ( const td : : td_api : : user & user )
2020-05-11 20:18:25 +02:00
{
2020-05-13 11:21:19 +02:00
// Prepend "id" so it's not accidentally equal to our phone number which is account name
return " id " + std : : to_string ( user . id_ ) ;
2020-05-11 20:18:25 +02:00
}
2020-06-06 20:12:12 +02:00
std : : vector < const td : : td_api : : user * > getUsersByPurpleName ( const char * buddyName , TdAccountData & account ,
const char * action )
2020-05-16 20:38:58 +02:00
{
2020-06-06 20:12:12 +02:00
std : : vector < const td : : td_api : : user * > result ;
2020-05-16 20:38:58 +02:00
2020-05-20 12:45:46 +02:00
int32_t userId = stringToUserId ( buddyName ) ;
2020-06-06 20:12:12 +02:00
if ( userId ! = 0 ) {
const td : : td_api : : user * tdUser = account . getUser ( userId ) ;
if ( tdUser ! = nullptr )
result . push_back ( tdUser ) ;
else if ( action )
purple_debug_warning ( config : : pluginId , " Cannot %s: no user with id %s \n " , action , buddyName ) ;
} else {
account . getUsersByDisplayName ( buddyName , result ) ;
if ( action ) {
if ( result . empty ( ) )
purple_debug_warning ( config : : pluginId , " Cannot %s: no user with display name '%s' \n " ,
action , buddyName ) ;
else if ( result . size ( ) ! = 1 )
purple_debug_warning ( config : : pluginId , " Cannot %s: more than one user with display name '%s' \n " ,
action , buddyName ) ;
}
2020-05-20 12:45:46 +02:00
}
2020-06-06 20:12:12 +02:00
return result ;
2020-05-20 12:45:46 +02:00
}
2020-05-11 20:18:25 +02:00
PurpleConversation * getImConversation ( PurpleAccount * account , const char * username )
{
PurpleConversation * conv = purple_find_conversation_with_account ( PURPLE_CONV_TYPE_IM , username , account ) ;
if ( conv = = NULL )
conv = purple_conversation_new ( PURPLE_CONV_TYPE_IM , account , username ) ;
return conv ;
}
2020-05-22 13:05:11 +02:00
PurpleConvChat * getChatConversation ( TdAccountData & account , const td : : td_api : : chat & chat ,
int chatPurpleId )
2020-05-11 20:18:25 +02:00
{
2020-06-11 00:04:29 +02:00
std : : string chatName = getPurpleChatName ( chat ) ;
2020-05-11 20:18:25 +02:00
bool newChatCreated = false ;
2020-07-10 22:02:41 +02:00
// If account logged off with chats open, these chats will be purple_conv_chat_left()'d but not
// purple_conversation_destroy()'d by purple_connection_destroy. So when logging back in,
// conversation will exist but not necessarily with correct libpurple id. Therefore, lookup by
// libpurple id using purple_find_chat cannot be used.
PurpleConversation * conv = purple_find_conversation_with_account ( PURPLE_CONV_TYPE_CHAT , chatName . c_str ( ) ,
account . purpleAccount ) ;
// Such pre-open chats will (unless some other logic intervenes) be inactive (as in,
// purple_conv_chat_has_left returns true) and if that's the case, serv_got_joined_chat must
// still be called to make them active and thus able to send or receive messages, because that's
// the kind of thing we were called for here.
if ( ( conv = = NULL ) | | purple_conv_chat_has_left ( purple_conversation_get_chat_data ( conv ) ) ) {
2020-05-11 20:18:25 +02:00
if ( chatPurpleId ! = 0 ) {
purple_debug_misc ( config : : pluginId , " Creating conversation for chat %s (purple id %d) \n " ,
chat . title_ . c_str ( ) , chatPurpleId ) ;
2020-05-22 13:05:11 +02:00
serv_got_joined_chat ( purple_account_get_connection ( account . purpleAccount ) , chatPurpleId , chatName . c_str ( ) ) ;
2020-07-10 22:02:41 +02:00
conv = purple_find_conversation_with_account ( PURPLE_CONV_TYPE_CHAT , chatName . c_str ( ) ,
account . purpleAccount ) ;
2020-05-11 20:18:25 +02:00
if ( conv = = NULL )
purple_debug_warning ( config : : pluginId , " Did not create conversation for chat %s \n " , chat . title_ . c_str ( ) ) ;
2020-05-16 19:25:01 +02:00
else {
2020-08-27 17:23:59 +02:00
// Sometimes when the group has just been created, or we left it and then got
// messageChatDeleteMember, the chat will not be in buddy list. In that case,
// libpurpleis going to use chatXXXXXXXXXXX as chat title. Set chat title explicitly
// to prevent that.
2020-05-22 13:05:11 +02:00
PurpleChat * purpleChat = purple_blist_find_chat ( account . purpleAccount , chatName . c_str ( ) ) ;
2020-05-16 19:25:01 +02:00
if ( ! purpleChat ) {
purple_debug_misc ( config : : pluginId , " Setting conversation title to '%s' \n " , chat . title_ . c_str ( ) ) ;
purple_conversation_set_title ( conv , chat . title_ . c_str ( ) ) ;
}
2020-05-11 20:18:25 +02:00
newChatCreated = true ;
2020-05-16 19:25:01 +02:00
}
2020-05-11 20:18:25 +02:00
} else
purple_debug_warning ( config : : pluginId , " No internal ID for chat %s \n " , chat . title_ . c_str ( ) ) ;
}
if ( conv ) {
PurpleConvChat * purpleChat = purple_conversation_get_chat_data ( conv ) ;
if ( purpleChat & & newChatCreated ) {
int32_t basicGroupId = getBasicGroupId ( chat ) ;
2020-05-22 13:05:11 +02:00
const td : : td_api : : basicGroupFullInfo * groupInfo = basicGroupId ? account . getBasicGroupInfo ( basicGroupId ) : nullptr ;
2020-05-11 20:18:25 +02:00
if ( groupInfo )
2020-06-02 17:25:06 +02:00
updateChatConversation ( purpleChat , * groupInfo , account ) ;
2020-06-01 11:12:26 +02:00
2020-07-05 15:01:24 +02:00
int32_t supergroupId = getSupergroupId ( chat ) ;
if ( supergroupId ) {
const td : : td_api : : supergroupFullInfo * supergroupInfo = account . getSupergroupInfo ( supergroupId ) ;
const td : : td_api : : chatMembers * members = account . getSupergroupMembers ( supergroupId ) ;
if ( supergroupInfo )
updateChatConversation ( purpleChat , * supergroupInfo , account ) ;
if ( members )
updateSupergroupChatMembers ( purpleChat , * members , account ) ;
}
2020-05-11 20:18:25 +02:00
}
return purpleChat ;
}
return NULL ;
}
2020-05-16 19:25:01 +02:00
PurpleConvChat * findChatConversation ( PurpleAccount * account , const td : : td_api : : chat & chat )
{
2020-06-11 00:04:29 +02:00
std : : string name = getPurpleChatName ( chat ) ;
2020-05-16 19:25:01 +02:00
PurpleConversation * conv = purple_find_conversation_with_account ( PURPLE_CONV_TYPE_CHAT ,
name . c_str ( ) , account ) ;
if ( conv )
return purple_conversation_get_chat_data ( conv ) ;
return NULL ;
}
2020-08-27 17:23:59 +02:00
void updatePrivateChat ( TdAccountData & account , const td : : td_api : : chat * chat , const td : : td_api : : user & user )
2020-05-22 12:49:10 +02:00
{
std : : string purpleUserName = getPurpleBuddyName ( user ) ;
2020-08-27 17:23:59 +02:00
std : : string alias = chat ? chat - > title_ : makeBasicDisplayName ( user ) ;
2020-05-22 12:49:10 +02:00
2020-05-22 13:05:11 +02:00
PurpleBuddy * buddy = purple_find_buddy ( account . purpleAccount , purpleUserName . c_str ( ) ) ;
2020-05-22 12:49:10 +02:00
if ( buddy = = NULL ) {
2020-08-27 17:23:59 +02:00
purple_debug_misc ( config : : pluginId , " Adding new buddy %s for user %s \n " ,
alias . c_str ( ) , purpleUserName . c_str ( ) ) ;
2020-05-22 12:49:10 +02:00
2020-05-22 13:05:11 +02:00
const ContactRequest * contactReq = account . findContactRequest ( user . id_ ) ;
2020-05-22 12:49:10 +02:00
PurpleGroup * group = ( contactReq & & ! contactReq - > groupName . empty ( ) ) ?
purple_find_group ( contactReq - > groupName . c_str ( ) ) : NULL ;
if ( group )
purple_debug_misc ( config : : pluginId , " Adding into group %s \n " , purple_group_get_name ( group ) ) ;
2020-08-27 17:23:59 +02:00
buddy = purple_buddy_new ( account . purpleAccount , purpleUserName . c_str ( ) , alias . c_str ( ) ) ;
2020-05-22 12:49:10 +02:00
purple_blist_add_buddy ( buddy , NULL , group , NULL ) ;
// If a new buddy has been added here, it means that there was updateNewChat with the private
// chat. This means either we added them to contacts or started messaging them, or they
// messaged us. Either way, there is no need to for any extra notification about new contact
// because the user will be aware anyway.
2020-06-07 01:14:06 +02:00
// Now, in case this buddy resulted from sending a message to group chat member
std : : string displayName = account . getDisplayName ( user ) ;
PurpleConversation * oldConv = purple_find_conversation_with_account ( PURPLE_CONV_TYPE_IM , displayName . c_str ( ) ,
account . purpleAccount ) ;
if ( oldConv ) {
2020-08-27 20:42:57 +02:00
purple_conv_im_write ( purple_conversation_get_im_data ( oldConv ) , " " ,
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-chat status update
2020-06-07 01:14:06 +02:00
_ ( " Future messages in this conversation will be shown in a different tab " ) ,
PURPLE_MESSAGE_SYSTEM , time ( NULL ) ) ;
}
2020-05-22 12:49:10 +02:00
} else {
2020-08-27 17:23:59 +02:00
purple_blist_alias_buddy ( buddy , alias . c_str ( ) ) ;
2020-06-04 22:57:01 +02:00
const char * oldPhotoIdStr = purple_blist_node_get_string ( PURPLE_BLIST_NODE ( buddy ) , BuddyOptions : : ProfilePhotoId ) ;
int64_t oldPhotoId = 0 ;
if ( oldPhotoIdStr )
sscanf ( oldPhotoIdStr , " % " G_GINT64_FORMAT , & oldPhotoId ) ;
if ( user . profile_photo_ & & user . profile_photo_ - > small_ )
{
const td : : td_api : : file & photo = * user . profile_photo_ - > small_ ;
if ( photo . local_ & & photo . local_ - > is_downloading_completed_ & &
( user . profile_photo_ - > id_ ! = oldPhotoId ) )
{
gchar * img = NULL ;
size_t len ;
GError * err = NULL ;
g_file_get_contents ( photo . local_ - > path_ . c_str ( ) , & img , & len , & err ) ;
if ( err ) {
purple_debug_warning ( config : : pluginId , " Failed to load profile photo %s for %s: %s \n " ,
photo . local_ - > path_ . c_str ( ) , purpleUserName . c_str ( ) , err - > message ) ;
g_error_free ( err ) ;
} else {
std : : string newPhotoIdStr = std : : to_string ( user . profile_photo_ - > id_ ) ;
purple_blist_node_set_string ( PURPLE_BLIST_NODE ( buddy ) , BuddyOptions : : ProfilePhotoId ,
newPhotoIdStr . c_str ( ) ) ;
purple_debug_info ( config : : pluginId , " Loaded new profile photo for %s (id %s) \n " ,
purpleUserName . c_str ( ) , newPhotoIdStr . c_str ( ) ) ;
purple_buddy_icons_set_for_user ( account . purpleAccount , purpleUserName . c_str ( ) ,
img , len , NULL ) ;
}
}
} else if ( oldPhotoId ) {
purple_debug_info ( config : : pluginId , " Removing profile photo from %s \n " , purpleUserName . c_str ( ) ) ;
purple_blist_node_remove_setting ( PURPLE_BLIST_NODE ( buddy ) , BuddyOptions : : ProfilePhotoId ) ;
purple_buddy_icons_set_for_user ( account . purpleAccount , purpleUserName . c_str ( ) , NULL , 0 , NULL ) ;
}
2020-05-22 12:49:10 +02:00
}
}
2020-05-22 13:46:37 +02:00
static void updateGroupChat ( PurpleAccount * purpleAccount , const td : : td_api : : chat & chat ,
2020-05-22 12:49:10 +02:00
const td : : td_api : : object_ptr < td : : td_api : : ChatMemberStatus > & groupStatus ,
const char * groupType , int32_t groupId )
2020-05-20 13:52:26 +02:00
{
if ( ! isGroupMember ( groupStatus ) ) {
purple_debug_misc ( config : : pluginId , " Skipping %s %d because we are not a member \n " ,
groupType , groupId ) ;
return ;
}
2020-06-11 00:04:29 +02:00
std : : string chatName = getPurpleChatName ( chat ) ;
2020-05-22 13:46:37 +02:00
PurpleChat * purpleChat = purple_blist_find_chat ( purpleAccount , chatName . c_str ( ) ) ;
2020-05-20 13:52:26 +02:00
if ( ! purpleChat ) {
purple_debug_misc ( config : : pluginId , " Adding new chat for %s %d (%s) \n " ,
groupType , groupId , chat . title_ . c_str ( ) ) ;
2020-05-22 13:46:37 +02:00
purpleChat = purple_chat_new ( purpleAccount , chat . title_ . c_str ( ) , getChatComponents ( chat ) ) ;
2020-05-20 13:52:26 +02:00
purple_blist_add_chat ( purpleChat , NULL , NULL ) ;
} else {
const char * oldName = purple_chat_get_name ( purpleChat ) ;
2020-05-20 14:25:26 +02:00
if ( chat . title_ ! = oldName ) {
purple_debug_misc ( config : : pluginId , " Renaming chat '%s' to '%s' \n " , oldName , chat . title_ . c_str ( ) ) ;
purple_blist_alias_chat ( purpleChat , chat . title_ . c_str ( ) ) ;
}
2020-05-20 13:52:26 +02:00
}
2020-06-04 22:57:01 +02:00
const char * oldPhotoId = purple_blist_node_get_string ( PURPLE_BLIST_NODE ( purpleChat ) , BuddyOptions : : ProfilePhotoId ) ;
if ( chat . photo_ & & chat . photo_ - > small_ )
{
const td : : td_api : : file & photo = * chat . photo_ - > small_ ;
if ( photo . local_ & & photo . local_ - > is_downloading_completed_ & & photo . remote_ & &
! photo . remote_ - > unique_id_ . empty ( ) & & ( ! oldPhotoId | | ( photo . remote_ - > unique_id_ ! = oldPhotoId ) ) )
{
gchar * img = NULL ;
size_t len ;
GError * err = NULL ;
g_file_get_contents ( photo . local_ - > path_ . c_str ( ) , & img , & len , & err ) ;
if ( err ) {
purple_debug_warning ( config : : pluginId , " Failed to load chat photo %s for %s: %s \n " ,
photo . local_ - > path_ . c_str ( ) , chat . title_ . c_str ( ) , err - > message ) ;
g_error_free ( err ) ;
} else {
purple_blist_node_set_string ( PURPLE_BLIST_NODE ( purpleChat ) , BuddyOptions : : ProfilePhotoId ,
photo . remote_ - > unique_id_ . c_str ( ) ) ;
purple_debug_info ( config : : pluginId , " Loaded new chat photo for %s (id %s) \n " ,
chat . title_ . c_str ( ) , photo . remote_ - > unique_id_ . c_str ( ) ) ;
purple_buddy_icons_node_set_custom_icon ( PURPLE_BLIST_NODE ( purpleChat ) ,
reinterpret_cast < guchar * > ( img ) , len ) ;
}
}
} else if ( oldPhotoId ) {
purple_debug_info ( config : : pluginId , " Removing chat photo from %s \n " , chat . title_ . c_str ( ) ) ;
purple_blist_node_remove_setting ( PURPLE_BLIST_NODE ( purpleChat ) , BuddyOptions : : ProfilePhotoId ) ;
purple_buddy_icons_node_set_custom_icon ( PURPLE_BLIST_NODE ( purpleChat ) , NULL , 0 ) ;
}
2020-05-20 13:52:26 +02:00
}
2020-05-22 13:05:11 +02:00
void updateBasicGroupChat ( TdAccountData & account , int32_t groupId )
2020-05-22 12:49:10 +02:00
{
2020-05-22 13:05:11 +02:00
const td : : td_api : : basicGroup * group = account . getBasicGroup ( groupId ) ;
const td : : td_api : : chat * chat = account . getBasicGroupChatByGroup ( groupId ) ;
2020-05-22 12:49:10 +02:00
if ( ! group )
purple_debug_misc ( config : : pluginId , " Basic group %d does not exist yet \n " , groupId ) ;
else if ( ! chat )
purple_debug_misc ( config : : pluginId , " Chat for basic group %d does not exist yet \n " , groupId ) ;
else
2020-05-22 13:05:11 +02:00
updateGroupChat ( account . purpleAccount , * chat , group - > status_ , " basic group " , groupId ) ;
2020-05-22 12:49:10 +02:00
}
2020-05-22 13:05:11 +02:00
void updateSupergroupChat ( TdAccountData & account , int32_t groupId )
2020-05-22 12:49:10 +02:00
{
2020-05-22 13:05:11 +02:00
const td : : td_api : : supergroup * group = account . getSupergroup ( groupId ) ;
const td : : td_api : : chat * chat = account . getSupergroupChatByGroup ( groupId ) ;
2020-05-22 12:49:10 +02:00
if ( ! group )
purple_debug_misc ( config : : pluginId , " Supergroup %d does not exist yet \n " , groupId ) ;
else if ( ! chat )
purple_debug_misc ( config : : pluginId , " Chat for supergroup %d does not exist yet \n " , groupId ) ;
else
2020-05-22 13:05:11 +02:00
updateGroupChat ( account . purpleAccount , * chat , group - > status_ , " supergroup " , groupId ) ;
2020-05-22 12:49:10 +02:00
}
2020-05-22 13:46:37 +02:00
void removeGroupChat ( PurpleAccount * purpleAccount , const td : : td_api : : chat & chat )
{
2020-06-11 00:04:29 +02:00
std : : string chatName = getPurpleChatName ( chat ) ;
2020-05-22 13:46:37 +02:00
PurpleChat * purpleChat = purple_blist_find_chat ( purpleAccount , chatName . c_str ( ) ) ;
if ( purpleChat )
purple_blist_remove_chat ( purpleChat ) ;
}
2020-06-07 14:07:58 +02:00
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 ;
}
2020-06-06 20:12:12 +02:00
void showMessageTextIm ( TdAccountData & account , const char * purpleUserName , const char * text ,
const char * notification , time_t timestamp , PurpleMessageFlags flags )
2020-05-11 20:18:25 +02:00
{
PurpleConversation * conv = NULL ;
if ( text ) {
2020-05-13 14:54:25 +02:00
if ( flags & PURPLE_MESSAGE_SEND ) {
2020-05-11 20:18:25 +02:00
// 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).
2020-05-22 13:05:11 +02:00
conv = getImConversation ( account . purpleAccount , purpleUserName ) ;
2020-06-05 01:18:48 +02:00
purple_conv_im_write ( purple_conversation_get_im_data ( conv ) ,
2020-06-11 11:29:05 +02:00
purple_account_get_name_for_display ( account . purpleAccount ) ,
2020-06-05 01:18:48 +02:00
text , flags , timestamp ) ;
2020-05-11 20:18:25 +02:00
} else {
2020-05-22 13:05:11 +02:00
serv_got_im ( purple_account_get_connection ( account . purpleAccount ) , purpleUserName , text ,
2020-05-13 14:54:25 +02:00
flags , timestamp ) ;
2020-05-11 20:18:25 +02:00
}
}
if ( notification ) {
if ( conv = = NULL )
2020-05-22 13:05:11 +02:00
conv = getImConversation ( account . purpleAccount , purpleUserName ) ;
2020-06-10 17:40:59 +02:00
purple_conv_im_write ( purple_conversation_get_im_data ( conv ) , purpleUserName , notification ,
2020-06-07 14:07:58 +02:00
getNotificationFlags ( flags ) , timestamp ) ;
2020-05-11 20:18:25 +02:00
}
}
2020-05-22 13:05:11 +02:00
static void showMessageTextChat ( TdAccountData & account , const td : : td_api : : chat & chat ,
2020-05-13 19:15:15 +02:00
const TgMessageInfo & message , const char * text ,
2020-05-22 13:05:11 +02:00
const char * notification , PurpleMessageFlags flags )
2020-05-11 20:18:25 +02:00
{
// Again, doing what facebook plugin does
2020-05-22 13:05:11 +02:00
int purpleId = account . getPurpleChatId ( chat . id_ ) ;
PurpleConvChat * conv = getChatConversation ( account , chat , purpleId ) ;
2020-05-11 20:18:25 +02:00
if ( text ) {
2020-05-13 14:54:25 +02:00
if ( flags & PURPLE_MESSAGE_SEND ) {
2020-05-11 20:18:25 +02:00
if ( conv )
2020-06-11 11:29:05 +02:00
purple_conv_chat_write ( conv , purple_account_get_name_for_display ( account . purpleAccount ) ,
text , flags , message . timestamp ) ;
2020-05-11 20:18:25 +02:00
} else {
if ( purpleId ! = 0 )
2020-05-22 13:05:11 +02:00
serv_got_chat_in ( purple_account_get_connection ( account . purpleAccount ) , purpleId ,
2020-05-13 19:15:15 +02:00
message . sender . empty ( ) ? " someone " : message . sender . c_str ( ) ,
flags , text , message . timestamp ) ;
2020-05-11 20:18:25 +02:00
}
}
if ( notification ) {
if ( conv )
2020-08-27 20:12:49 +02:00
// 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 ) ;
2020-05-11 20:18:25 +02:00
}
}
2020-06-06 11:16:43 +02:00
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 ;
}
2020-08-27 17:32:17 +02:00
std : : string makeInlineImageText ( int imgstoreId )
{
return " \n <img id= \" " + std : : to_string ( imgstoreId ) + " \" > " ;
}
2020-05-22 13:05:11 +02:00
static std : : string quoteMessage ( const td : : td_api : : message * message , TdAccountData & account )
2020-05-16 13:38:00 +02:00
{
const td : : td_api : : user * originalAuthor = nullptr ;
if ( message )
2020-05-22 13:05:11 +02:00
originalAuthor = account . getUser ( message - > sender_user_id_ ) ;
2020-05-16 13:38:00 +02:00
std : : string originalName ;
if ( originalAuthor )
2020-05-29 19:39:22 +02:00
originalName = account . getDisplayName ( * originalAuthor ) ;
2020-08-19 22:51:21 +02:00
else {
2020-09-02 21:20:53 +02:00
// message == NULL means it could not be fetched, or took too long to fetch
2020-08-19 22:51:21 +02:00
// 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:"...
2020-08-19 23:40:46 +02:00
originalName = _ ( " Unknown user " ) ;
2020-08-19 22:51:21 +02:00
}
2020-05-16 13:38:00 +02:00
std : : string text ;
2020-08-19 22:51:21 +02:00
if ( ! message | | ! message - > content_ ) {
2020-08-20 23:13:11 +02:00
// TRANSLATOR: In-chat placeholder when something unknown is being replied to.
2020-05-16 13:38:00 +02:00
text = _ ( " [message unavailable] " ) ;
2020-08-19 22:51:21 +02:00
} else switch ( message - > content_ - > get_id ( ) ) {
2020-05-16 13:38:00 +02:00
case td : : td_api : : messageText : : ID : {
const td : : td_api : : messageText & messageText = static_cast < const td : : td_api : : messageText & > ( * message - > content_ ) ;
if ( messageText . text_ )
2020-06-06 11:16:43 +02:00
text = getMessageText ( * messageText . text_ ) ;
2020-05-16 13:38:00 +02:00
else
text = " " ;
break ;
}
case td : : td_api : : messagePhoto : : ID : {
const td : : td_api : : messagePhoto & photo = static_cast < const td : : td_api : : messagePhoto & > ( * message - > content_ ) ;
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-line placeholder when a photo is being replied to.
2020-05-16 13:38:00 +02:00
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_ ) ;
2020-08-19 22:51:21 +02:00
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")
2020-08-20 23:13:11 +02:00
text = formatMessage ( _ ( " [file: {0} ({1})] " ) , { document . document_ - > file_name_ ,
2020-05-16 13:38:00 +02:00
document . document_ - > mime_type_ } ) ;
2020-08-19 22:51:21 +02:00
} else {
2020-09-02 21:20:53 +02:00
// Not supposed to be possible, but just in case
text = " [file] " ;
2020-08-19 22:51:21 +02:00
}
2020-05-16 13:38:00 +02:00
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_ ) ;
2020-08-19 22:51:21 +02:00
if ( video . video_ ) {
// TRANSLATOR: In-line placeholder when a video is being replied to. Argument will be the file name.
2020-05-16 13:38:00 +02:00
text = formatMessage ( _ ( " [video: {}] " ) , video . video_ - > file_name_ ) ;
2020-08-19 22:51:21 +02:00
} else {
2020-09-02 21:20:53 +02:00
// Not supposed to be possible, but just in case
text = " [video] " ;
2020-08-19 22:51:21 +02:00
}
2020-05-16 13:38:00 +02:00
if ( video . caption_ )
text + = " " + video . caption_ - > text_ ;
break ;
}
case td : : td_api : : messageSticker : : ID :
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-line placeholder when a sticker is being replied to.
2020-05-16 13:38:00 +02:00
text = _ ( " [sticker] " ) ;
break ;
default :
2020-09-02 21:20:53 +02:00
text = ' [ ' + getUnsupportedMessageDescription ( * message - > content_ ) + ' ] ' ;
break ;
2020-05-16 13:38:00 +02:00
}
for ( unsigned i = 0 ; i < text . size ( ) ; i + + )
if ( text [ i ] = = ' \n ' ) text [ i ] = ' ' ;
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-chat notification of a reply. Arguments will be username and the original text or description thereof. Please preserve the HTML.
2020-08-20 19:13:30 +02:00
return formatMessage ( _ ( " <b>> {0} wrote:</b> \n > {1} " ) , { originalName , text } ) ;
2020-05-16 13:38:00 +02:00
}
2020-05-22 13:05:11 +02:00
void showMessageText ( TdAccountData & account , const td : : td_api : : chat & chat , const TgMessageInfo & message ,
const char * text , const char * notification , uint32_t extraFlags )
2020-05-12 09:35:40 +02:00
{
2020-05-13 19:15:15 +02:00
PurpleMessageFlags directionFlag = message . outgoing ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV ;
PurpleMessageFlags flags = ( PurpleMessageFlags ) ( extraFlags | directionFlag ) ;
2020-08-23 19:11:46 +02:00
if ( message . outgoing & & ! message . sentLocally )
flags = ( PurpleMessageFlags ) ( flags | PURPLE_MESSAGE_REMOTE_SEND ) ;
2020-05-13 19:15:15 +02:00
2020-05-16 13:38:00 +02:00
std : : string newText ;
2020-05-16 15:32:56 +02:00
if ( message . repliedMessageId ! = 0 )
2020-05-30 10:07:55 +02:00
newText = quoteMessage ( message . repliedMessage . get ( ) , account ) ;
2020-05-16 15:32:56 +02:00
if ( ! message . forwardedFrom . empty ( ) ) {
if ( ! newText . empty ( ) )
2020-05-16 13:38:00 +02:00
newText + = " \n " ;
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-chat notification of forward. Argument will be a username. Please preserve the HTML.
2020-05-16 15:32:56 +02:00
newText + = formatMessage ( _ ( " <b>Forwarded from {}:</b> " ) , message . forwardedFrom ) ;
2020-05-16 13:38:00 +02:00
}
2020-05-16 15:32:56 +02:00
if ( text ) {
if ( ! newText . empty ( ) )
newText + = " \n " ;
newText + = text ;
}
if ( ! newText . empty ( ) )
text = newText . c_str ( ) ;
2020-05-16 13:38:00 +02:00
2020-05-22 13:05:11 +02:00
const td : : td_api : : user * privateUser = account . getUserByPrivateChat ( chat ) ;
2020-05-13 11:21:19 +02:00
if ( privateUser ) {
2020-06-06 20:12:12 +02:00
std : : string userName ;
if ( isChatInContactList ( chat , privateUser ) )
userName = getPurpleBuddyName ( * privateUser ) ;
else
userName = account . getDisplayName ( * privateUser ) ;
2020-05-13 19:15:15 +02:00
showMessageTextIm ( account , userName . c_str ( ) , text , notification , message . timestamp , flags ) ;
2020-05-13 11:21:19 +02:00
}
2020-05-12 09:35:40 +02:00
if ( getBasicGroupId ( chat ) | | getSupergroupId ( chat ) )
2020-05-22 13:05:11 +02:00
showMessageTextChat ( account , chat , message , text , notification , flags ) ;
2020-05-12 09:35:40 +02:00
}
2020-06-07 14:07:58 +02:00
void showChatNotification ( TdAccountData & account , const td : : td_api : : chat & chat ,
const char * notification , PurpleMessageFlags flags )
{
TgMessageInfo messageInfo ;
messageInfo . type = TgMessageInfo : : Type : : Other ;
messageInfo . timestamp = ( flags = = PURPLE_MESSAGE_NO_LOG ) ? 0 : time ( NULL ) ;
messageInfo . outgoing = true ;
showMessageText ( account , chat , messageInfo , NULL , notification , flags ) ;
}
2020-07-25 12:49:38 +02:00
std : : string makeBasicDisplayName ( const td : : td_api : : user & user )
{
std : : string result = user . first_name_ ;
if ( ! result . empty ( ) & & ! user . last_name_ . empty ( ) )
result + = ' ' ;
result + = user . last_name_ ;
return result ;
}
2020-05-12 09:35:40 +02:00
std : : string getSenderPurpleName ( const td : : td_api : : chat & chat , const td : : td_api : : message & message ,
2020-05-22 13:05:11 +02:00
TdAccountData & account )
2020-05-12 09:35:40 +02:00
{
if ( ! message . is_outgoing_ & & ( getBasicGroupId ( chat ) | | getSupergroupId ( chat ) ) ) {
if ( message . sender_user_id_ )
2020-05-29 19:39:22 +02:00
return account . getDisplayName ( message . sender_user_id_ ) ;
2020-05-12 09:35:40 +02:00
else if ( ! message . author_signature_ . empty ( ) )
return message . author_signature_ ;
2020-08-19 22:51:21 +02:00
else if ( message . is_channel_post_ ) {
2020-09-02 21:20:53 +02:00
// TRANSLATOR: The "sender" of a message that was posted to a channel. Will be used like a username.
2020-08-13 13:40:34 +02:00
return _ ( " Channel post " ) ;
2020-08-19 22:51:21 +02:00
} else if ( message . forward_info_ & & message . forward_info_ - > origin_ )
2020-05-12 09:35:40 +02:00
switch ( message . forward_info_ - > origin_ - > get_id ( ) ) {
case td : : td_api : : messageForwardOriginUser : : ID :
2020-05-29 19:39:22 +02:00
return account . getDisplayName ( static_cast < const td : : td_api : : messageForwardOriginUser & > ( * message . forward_info_ - > origin_ ) . sender_user_id_ ) ;
2020-05-12 09:35:40 +02:00
case td : : td_api : : messageForwardOriginHiddenUser : : ID :
return static_cast < const td : : td_api : : messageForwardOriginHiddenUser & > ( * message . forward_info_ - > origin_ ) . sender_name_ ;
case td : : td_api : : messageForwardOriginChannel : : ID :
return static_cast < const td : : td_api : : messageForwardOriginChannel & > ( * message . forward_info_ - > origin_ ) . author_signature_ ;
}
}
// For outgoing messages, our name will be used instead
// For private chats, sender name will be determined from the chat instead
return " " ;
}
2020-05-16 15:32:56 +02:00
std : : string getForwardSource ( const td : : td_api : : messageForwardInfo & forwardInfo ,
2020-05-22 13:05:11 +02:00
TdAccountData & account )
2020-05-16 15:32:56 +02:00
{
if ( ! forwardInfo . origin_ )
return " " ;
switch ( forwardInfo . origin_ - > get_id ( ) ) {
case td : : td_api : : messageForwardOriginUser : : ID :
2020-05-29 19:39:22 +02:00
return account . getDisplayName ( static_cast < const td : : td_api : : messageForwardOriginUser & > ( * forwardInfo . origin_ ) . sender_user_id_ ) ;
2020-05-16 15:32:56 +02:00
case td : : td_api : : messageForwardOriginHiddenUser : : ID :
return static_cast < const td : : td_api : : messageForwardOriginHiddenUser & > ( * forwardInfo . origin_ ) . sender_name_ ;
case td : : td_api : : messageForwardOriginChannel : : ID : {
2020-05-22 13:05:11 +02:00
const td : : td_api : : chat * chat = account . getChat ( static_cast < const td : : td_api : : messageForwardOriginChannel & > ( * forwardInfo . origin_ ) . chat_id_ ) ;
2020-05-16 15:32:56 +02:00
if ( chat )
return chat - > title_ ;
}
}
return " " ;
}
2020-05-14 14:42:05 +02:00
void getNamesFromAlias ( const char * alias , std : : string & firstName , std : : string & lastName )
{
if ( ! alias ) alias = " " ;
const char * name1end = alias ;
while ( * name1end & & isspace ( * name1end ) ) name1end + + ;
while ( * name1end & & ! isspace ( * name1end ) ) name1end + + ;
firstName = std : : string ( alias , name1end - alias ) ;
const char * name2start = name1end ;
while ( * name2start & & isspace ( * name2start ) ) name2start + + ;
lastName = name2start ;
}
2020-05-22 14:48:17 +02:00
static void findChatsByComponents ( PurpleBlistNode * node ,
2020-08-13 13:34:23 +02:00
const char * joinString , const char * groupName , int groupType ,
2020-05-12 19:37:59 +02:00
std : : vector < PurpleChat * > & result )
{
PurpleBlistNodeType nodeType = purple_blist_node_get_type ( node ) ;
if ( nodeType = = PURPLE_BLIST_CHAT_NODE ) {
2020-08-13 13:34:23 +02:00
PurpleChat * chat = PURPLE_CHAT ( node ) ;
GHashTable * components = purple_chat_get_components ( chat ) ;
const char * nodeName = getChatName ( components ) ;
const char * nodeJoinString = getChatJoinString ( components ) ;
const char * nodeGroupName = getChatGroupName ( components ) ;
int nodeGroupType = getChatGroupType ( components ) ;
2020-05-22 14:48:17 +02:00
if ( ! nodeName ) nodeName = " " ;
2020-08-13 13:34:23 +02:00
if ( ! nodeJoinString ) nodeJoinString = " " ;
2020-05-22 14:48:17 +02:00
if ( ! nodeGroupName ) nodeGroupName = " " ;
2020-08-13 13:34:23 +02:00
if ( ! strcmp ( nodeName , " " ) & & ! strcmp ( nodeJoinString , joinString ) ) {
if ( ( * joinString ! = ' \0 ' ) | |
2020-05-22 14:48:17 +02:00
( ! strcmp ( nodeGroupName , groupName ) & & ( nodeGroupType = = groupType ) ) )
{
result . push_back ( chat ) ;
}
}
2020-05-12 19:37:59 +02:00
}
for ( PurpleBlistNode * child = purple_blist_node_get_first_child ( node ) ; child ;
child = purple_blist_node_get_sibling_next ( child ) )
{
2020-08-13 13:34:23 +02:00
findChatsByComponents ( child , joinString , groupName , groupType , result ) ;
2020-05-12 19:37:59 +02:00
}
}
2020-08-13 13:34:23 +02:00
std : : vector < PurpleChat * > findChatsByJoinString ( const std : : string & joinString )
2020-05-12 19:37:59 +02:00
{
std : : vector < PurpleChat * > result ;
for ( PurpleBlistNode * root = purple_blist_get_root ( ) ; root ;
root = purple_blist_node_get_sibling_next ( root ) ) // LOL
{
2020-08-13 13:34:23 +02:00
findChatsByComponents ( root , joinString . c_str ( ) , " " , 0 , result ) ;
2020-05-22 14:48:17 +02:00
}
return result ;
}
std : : vector < PurpleChat * > findChatsByNewGroup ( const char * name , int type )
{
std : : vector < PurpleChat * > result ;
for ( PurpleBlistNode * root = purple_blist_get_root ( ) ; root ;
root = purple_blist_node_get_sibling_next ( root ) ) // LOL
{
findChatsByComponents ( root , " " , name , type , result ) ;
2020-05-12 19:37:59 +02:00
}
return result ;
}
2020-07-05 15:01:24 +02:00
static void setChatMembers ( PurpleConvChat * purpleChat ,
const std : : vector < td : : td_api : : object_ptr < td : : td_api : : chatMember > > & members ,
2020-06-02 17:25:06 +02:00
const TdAccountData & account )
2020-05-11 20:18:25 +02:00
{
2020-05-22 13:05:11 +02:00
GList * flags = NULL ;
2020-05-11 20:18:25 +02:00
std : : vector < std : : string > nameData ;
2020-05-11 23:17:48 +02:00
2020-07-05 15:01:24 +02:00
for ( const auto & member : members ) {
2020-05-11 23:36:26 +02:00
if ( ! member | | ! isGroupMember ( member - > status_ ) )
continue ;
2020-05-22 13:05:11 +02:00
const td : : td_api : : user * user = account . getUser ( member - > user_id_ ) ;
2020-06-06 21:20:39 +02:00
if ( ! user | | ( user - > type_ & & ( user - > type_ - > get_id ( ) = = td : : td_api : : userTypeDeleted : : ID ) ) )
2020-05-11 23:36:26 +02:00
continue ;
2020-05-16 20:38:58 +02:00
std : : string userName = getPurpleBuddyName ( * user ) ;
2020-05-13 11:21:19 +02:00
const char * phoneNumber = getCanonicalPhoneNumber ( user - > phone_number_ . c_str ( ) ) ;
2020-05-22 13:05:11 +02:00
if ( purple_find_buddy ( account . purpleAccount , userName . c_str ( ) ) )
2020-05-13 11:21:19 +02:00
// libpurple will be able to map user name to alias because there is a buddy
nameData . emplace_back ( userName ) ;
2020-05-22 13:05:11 +02:00
else if ( ! strcmp ( getCanonicalPhoneNumber ( purple_account_get_username ( account . purpleAccount ) ) , phoneNumber ) )
2020-05-13 11:21:19 +02:00
// This is us, so again libpurple will map phone number to alias
2020-05-22 13:05:11 +02:00
nameData . emplace_back ( purple_account_get_username ( account . purpleAccount ) ) ;
2020-05-13 11:21:19 +02:00
else {
// Use first and last name instead
2020-05-29 19:39:22 +02:00
std : : string displayName = account . getDisplayName ( * user ) ;
2020-05-11 23:36:26 +02:00
nameData . emplace_back ( displayName ) ;
2020-05-11 20:18:25 +02:00
}
2020-05-11 23:17:48 +02:00
2020-05-11 23:36:26 +02:00
PurpleConvChatBuddyFlags flag ;
if ( member - > status_ - > get_id ( ) = = td : : td_api : : chatMemberStatusCreator : : ID )
flag = PURPLE_CBFLAGS_FOUNDER ;
else if ( member - > status_ - > get_id ( ) = = td : : td_api : : chatMemberStatusAdministrator : : ID )
flag = PURPLE_CBFLAGS_OP ;
else
flag = PURPLE_CBFLAGS_NONE ;
flags = g_list_append ( flags , GINT_TO_POINTER ( flag ) ) ;
}
2020-05-15 14:21:33 +02:00
GList * names = NULL ;
for ( const std : : string & name : nameData )
names = g_list_append ( names , const_cast < char * > ( name . c_str ( ) ) ) ;
2020-05-16 12:27:54 +02:00
purple_conv_chat_clear_users ( purpleChat ) ;
2020-05-11 20:18:25 +02:00
purple_conv_chat_add_users ( purpleChat , names , NULL , flags , false ) ;
2020-05-11 20:26:41 +02:00
g_list_free ( names ) ;
g_list_free ( flags ) ;
2020-05-11 20:18:25 +02:00
}
2020-05-15 19:46:07 +02:00
2020-06-02 17:25:06 +02:00
void updateChatConversation ( PurpleConvChat * purpleChat , const td : : td_api : : basicGroupFullInfo & groupInfo ,
const TdAccountData & account )
{
purple_conv_chat_set_topic ( purpleChat , NULL , groupInfo . description_ . c_str ( ) ) ;
2020-07-05 15:01:24 +02:00
setChatMembers ( purpleChat , groupInfo . members_ , account ) ;
2020-06-02 17:25:06 +02:00
}
void updateChatConversation ( PurpleConvChat * purpleChat , const td : : td_api : : supergroupFullInfo & groupInfo ,
const TdAccountData & account )
{
purple_conv_chat_set_topic ( purpleChat , NULL , groupInfo . description_ . c_str ( ) ) ;
}
2020-07-05 15:01:24 +02:00
void updateSupergroupChatMembers ( PurpleConvChat * purpleChat , const td : : td_api : : chatMembers & members ,
const TdAccountData & account )
{
setChatMembers ( purpleChat , members . members_ , account ) ;
}
2020-06-02 17:25:06 +02:00
2020-05-15 19:46:07 +02:00
struct MessagePart {
2020-06-06 13:57:36 +02:00
bool isImage = false ;
int imageId = 0 ;
2020-05-15 19:46:07 +02:00
std : : string text ;
} ;
2020-06-06 13:57:36 +02:00
static size_t splitTextChunk ( MessagePart & part , const char * text , size_t length , TdAccountData & account )
{
enum { MIN_LENGTH_LIMIT = 8 } ;
unsigned lengthLimit = part . isImage ? account . options . maxCaptionLength : account . options . maxMessageLength ;
if ( lengthLimit = = 0 )
purple_debug_warning ( config : : pluginId , " No %s length limit \n " , part . isImage ? " caption " : " message " ) ;
else if ( lengthLimit < = MIN_LENGTH_LIMIT )
purple_debug_warning ( config : : pluginId , " %u is a ridiculous %s length limit \n " ,
lengthLimit , part . isImage ? " caption " : " message " ) ;
if ( ( lengthLimit < = MIN_LENGTH_LIMIT ) | | ( length < = lengthLimit ) ) {
part . text = std : : string ( text , length ) ;
return length ;
}
// Try to truncate at a line break, with no lower length limit in case of image caption
unsigned newlineSplitLowerLimit = part . isImage ? 1 : lengthLimit / 2 ;
for ( unsigned chunkLength = lengthLimit ; chunkLength > = newlineSplitLowerLimit ; chunkLength - - )
if ( text [ chunkLength - 1 ] = = ' \n ' ) {
part . text = std : : string ( text , chunkLength - 1 ) ;
return chunkLength ;
}
// Try to truncate to a whole number of utf-8 characters
size_t chunkLen ;
const char * pos = g_utf8_find_prev_char ( text , text + lengthLimit ) ;
if ( pos ! = NULL ) {
const char * next = g_utf8_find_next_char ( pos , NULL ) ;
if ( next = = text + lengthLimit )
chunkLen = lengthLimit ;
else
chunkLen = pos - text ;
} else
chunkLen = lengthLimit ;
part . text = std : : string ( text , chunkLen ) ;
return chunkLen ;
}
static void appendText ( std : : vector < MessagePart > & parts , const char * s , size_t len , TdAccountData & account )
2020-05-16 00:16:06 +02:00
{
2020-06-06 11:16:43 +02:00
if ( len ! = 0 ) {
if ( parts . empty ( ) )
parts . emplace_back ( ) ;
2020-06-06 13:57:36 +02:00
2020-06-06 11:16:43 +02:00
std : : string sourceText ( s , len ) ;
2020-08-18 22:41:02 +02:00
char * halfNewText = purple_markup_strip_html ( sourceText . c_str ( ) ) ;
char * newText = purple_unescape_html ( halfNewText ) ;
g_free ( halfNewText ) ;
2020-06-06 13:57:36 +02:00
const char * remaining = newText ;
size_t lenRemaining = strlen ( newText ) ;
while ( lenRemaining ) {
size_t chunkLength = splitTextChunk ( parts . back ( ) , remaining , lenRemaining , account ) ;
lenRemaining - = chunkLength ;
remaining + = chunkLength ;
if ( lenRemaining )
parts . emplace_back ( ) ;
}
2020-06-06 11:16:43 +02:00
g_free ( newText ) ;
}
2020-05-16 00:16:06 +02:00
}
2020-06-06 13:57:36 +02:00
static void parseMessage ( const char * message , std : : vector < MessagePart > & parts , TdAccountData & account )
2020-05-15 19:46:07 +02:00
{
2020-05-16 00:16:06 +02:00
parts . clear ( ) ;
if ( ! message )
return ;
const char * s = message ;
const char * textStart = message ;
while ( * s ) {
bool isImage = false ;
long imageId ;
char * pastImage = NULL ;
if ( ! strncasecmp ( s , " <img id= \" " , 9 ) ) {
const char * idString = s + 9 ;
imageId = strtol ( idString , & pastImage , 10 ) ;
if ( ( pastImage ! = idString ) & & ! strncmp ( pastImage , " \" > " , 2 ) & & ( imageId < = INT32_MAX ) & &
( imageId > = INT32_MIN ) )
{
isImage = true ;
pastImage + = 2 ;
2020-06-06 13:57:36 +02:00
if ( * pastImage = = ' \n ' )
pastImage + + ;
2020-05-16 00:16:06 +02:00
}
}
if ( isImage ) {
2020-06-06 13:57:36 +02:00
appendText ( parts , textStart , s - textStart , account ) ;
2020-05-16 00:16:06 +02:00
parts . emplace_back ( ) ;
parts . back ( ) . isImage = true ;
parts . back ( ) . imageId = imageId ;
s = pastImage ;
textStart = pastImage ;
} else
s + + ;
}
2020-06-06 13:57:36 +02:00
appendText ( parts , textStart , s - textStart , account ) ;
2020-05-16 00:16:06 +02:00
}
2020-06-06 13:57:36 +02:00
int transmitMessage ( int64_t chatId , const char * message , TdTransceiver & transceiver ,
TdAccountData & account , TdTransceiver : : ResponseCb response )
2020-05-15 19:46:07 +02:00
{
std : : vector < MessagePart > parts ;
2020-06-06 13:57:36 +02:00
parseMessage ( message , parts , account ) ;
if ( parts . size ( ) > MAX_MESSAGE_PARTS )
return - E2BIG ;
2020-05-15 19:46:07 +02:00
for ( const MessagePart & input : parts ) {
td : : td_api : : object_ptr < td : : td_api : : sendMessage > sendMessageRequest = td : : td_api : : make_object < td : : td_api : : sendMessage > ( ) ;
sendMessageRequest - > chat_id_ = chatId ;
2020-05-16 00:16:06 +02:00
char * tempFileName = NULL ;
bool hasImage = false ;
2020-05-15 19:46:07 +02:00
2020-05-16 00:16:06 +02:00
if ( input . isImage )
hasImage = saveImage ( input . imageId , & tempFileName ) ;
2020-05-15 19:46:07 +02:00
2020-05-16 00:16:06 +02:00
if ( hasImage ) {
2020-05-15 19:46:07 +02:00
td : : td_api : : object_ptr < td : : td_api : : inputMessagePhoto > content = td : : td_api : : make_object < td : : td_api : : inputMessagePhoto > ( ) ;
2020-05-16 00:16:06 +02:00
content - > photo_ = td : : td_api : : make_object < td : : td_api : : inputFileLocal > ( tempFileName ) ;
2020-05-15 19:46:07 +02:00
content - > caption_ = td : : td_api : : make_object < td : : td_api : : formattedText > ( ) ;
content - > caption_ - > text_ = input . text ;
sendMessageRequest - > input_message_content_ = std : : move ( content ) ;
2020-05-16 00:16:06 +02:00
purple_debug_misc ( config : : pluginId , " Sending photo %s \n " , tempFileName ) ;
2020-05-15 19:46:07 +02:00
} else {
td : : td_api : : object_ptr < td : : td_api : : inputMessageText > content = td : : td_api : : make_object < td : : td_api : : inputMessageText > ( ) ;
content - > text_ = td : : td_api : : make_object < td : : td_api : : formattedText > ( ) ;
2020-05-16 00:16:06 +02:00
content - > text_ - > text_ = input . text ;
2020-05-15 19:46:07 +02:00
sendMessageRequest - > input_message_content_ = std : : move ( content ) ;
}
2020-07-26 14:51:51 +02:00
int64_t chatId = sendMessageRequest - > chat_id_ ;
uint64_t requestId = transceiver . sendQuery ( std : : move ( sendMessageRequest ) , response ) ;
account . addPendingRequest < SendMessageRequest > ( requestId , chatId , tempFileName ) ;
2020-06-05 20:14:17 +02:00
if ( tempFileName )
2020-05-16 00:16:06 +02:00
g_free ( tempFileName ) ;
2020-05-15 19:46:07 +02:00
}
2020-06-06 13:57:36 +02:00
return 0 ;
2020-05-15 19:46:07 +02:00
}
2020-05-26 20:06:09 +02:00
2020-05-29 12:16:03 +02:00
std : : string getSenderDisplayName ( const td : : td_api : : chat & chat , const TgMessageInfo & message ,
PurpleAccount * account )
{
if ( message . outgoing )
2020-06-10 17:32:01 +02:00
return purple_account_get_name_for_display ( account ) ;
2020-05-29 12:16:03 +02:00
else if ( isPrivateChat ( chat ) )
return chat . title_ ;
else
return message . sender ;
}
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 ;
}
2020-08-14 22:07:22 +02:00
void showGenericFileInline ( const td : : td_api : : chat & chat , const TgMessageInfo & message ,
const std : : string & filePath , const std : : string & fileDescription ,
TdAccountData & account )
2020-05-29 12:43:09 +02:00
{
if ( filePath . find ( ' " ' ) ! = std : : string : : npos ) {
std : : string notice = makeNoticeWithSender ( chat , message , " Cannot show file: path contains quotes " ,
account . purpleAccount ) ;
showMessageText ( account , chat , message , NULL , notice . c_str ( ) ) ;
} else {
std : : string text = " <a href= \" file:// " + filePath + " \" > " + fileDescription + " </a> " ;
showMessageText ( account , chat , message , text . c_str ( ) , NULL ) ;
}
}
2020-06-05 20:14:17 +02:00
void notifySendFailed ( const td : : td_api : : updateMessageSendFailed & sendFailed , TdAccountData & account )
{
if ( sendFailed . message_ ) {
const td : : td_api : : chat * chat = account . getChat ( sendFailed . message_ - > chat_id_ ) ;
if ( chat ) {
TgMessageInfo messageInfo ;
messageInfo . type = TgMessageInfo : : Type : : Other ;
messageInfo . timestamp = sendFailed . message_ - > date_ ;
messageInfo . outgoing = true ;
std : : string errorMessage = formatMessage ( errorCodeMessage ( ) , { std : : to_string ( sendFailed . error_code_ ) ,
sendFailed . error_message_ } ) ;
2020-08-19 22:51:21 +02:00
// TRANSLATOR: In-chat error message, argument will be text.
2020-06-05 20:14:17 +02:00
errorMessage = formatMessage ( _ ( " Failed to send message: {} " ) , errorMessage ) ;
showMessageText ( account , * chat , messageInfo , NULL , errorMessage . c_str ( ) ) ;
}
}
}
2020-05-31 15:55:28 +02:00
static void closeSecretChat ( int32_t secretChatId , TdTransceiver & transceiver )
{
transceiver . sendQuery ( td : : td_api : : make_object < td : : td_api : : closeSecretChat > ( secretChatId ) , nullptr ) ;
}
static void secretChatNotSupported ( int32_t secretChatId , const std : : string & userDescription ,
TdTransceiver & transceiver , PurpleAccount * purpleAccount )
{
closeSecretChat ( secretChatId , transceiver ) ;
2020-09-02 21:20:53 +02:00
std : : string message = formatMessage ( " Rejected secret chat with {} " , userDescription ) ;
2020-05-31 15:55:28 +02:00
purple_notify_info ( purple_account_get_connection ( purpleAccount ) ,
2020-09-02 21:20:53 +02:00
" Secret chat " , message . c_str ( ) ,
" Secret chats not supported " ) ;
2020-05-31 15:55:28 +02:00
}
struct SecretChatInfo {
int32_t secretChatId ;
std : : string userDescription ;
TdTransceiver * transceiver ;
PurpleAccount * purpleAccount ;
} ;
static void acceptSecretChatCb ( SecretChatInfo * data )
{
std : : unique_ptr < SecretChatInfo > info ( data ) ;
secretChatNotSupported ( info - > secretChatId , info - > userDescription , * info - > transceiver , info - > purpleAccount ) ;
}
static void discardSecretChatCb ( SecretChatInfo * data )
{
std : : unique_ptr < SecretChatInfo > info ( data ) ;
closeSecretChat ( info - > secretChatId , * info - > transceiver ) ;
}
void updateSecretChat ( td : : td_api : : object_ptr < td : : td_api : : secretChat > secretChat ,
TdTransceiver & transceiver , TdAccountData & account )
{
if ( ! secretChat ) return ;
int32_t secretChatId = secretChat - > id_ ;
bool isExisting = account . getSecretChat ( secretChatId ) ;
const td : : td_api : : user * user = account . getUser ( secretChat - > user_id_ ) ;
account . addSecretChat ( std : : move ( secretChat ) ) ;
std : : string userDescription ;
if ( user )
userDescription = ' \' ' + account . getDisplayName ( * user ) + ' \' ' ;
2020-08-19 22:51:21 +02:00
else {
2020-09-02 21:20:53 +02:00
// Not supposed to be possible, because every user id should be preceded by user info
userDescription = " (unknown user) " ;
2020-08-19 22:51:21 +02:00
}
2020-05-31 15:55:28 +02:00
if ( ! isExisting ) {
const char * secretChatHandling = purple_account_get_string ( account . purpleAccount ,
AccountOptions : : AcceptSecretChats ,
AccountOptions : : AcceptSecretChatsDefault ) ;
if ( ! strcmp ( secretChatHandling , AccountOptions : : AcceptSecretChatsNever ) ) {
closeSecretChat ( secretChatId , transceiver ) ;
2020-08-19 22:51:21 +02:00
// TRANSLATOR: Dialog content, argument will be a username
2020-05-31 15:55:28 +02:00
std : : string message = formatMessage ( _ ( " Rejected secret chat with {} " ) , userDescription ) ;
purple_notify_info ( purple_account_get_connection ( account . purpleAccount ) ,
2020-08-19 22:51:21 +02:00
// TRANSLATOR: Dialog title
2020-05-31 15:55:28 +02:00
_ ( " Secret chat " ) , message . c_str ( ) , NULL ) ;
} else if ( ! strcmp ( secretChatHandling , AccountOptions : : AcceptSecretChatsAlways ) )
secretChatNotSupported ( secretChatId , userDescription , transceiver , account . purpleAccount ) ;
else {
2020-08-19 22:51:21 +02:00
// TRANSLATOR: Dialog content, argument will be a username, options will be "_Accept" and "_Cancel".
2020-05-31 15:55:28 +02:00
std : : string message = formatMessage ( _ ( " Accept secret chat with {} on this device? " ) , userDescription ) ;
SecretChatInfo * data = new SecretChatInfo { secretChatId , userDescription , & transceiver , account . purpleAccount } ;
2020-07-06 18:41:20 +02:00
purple_request_action ( purple_account_get_connection ( account . purpleAccount ) ,
2020-08-19 22:51:21 +02:00
// TRANSLATOR: Dialog title
_ ( " Secret chat " ) , message . c_str ( ) ,
// TRANSLATOR: Dialog secondary content. Options will be "_Accept" and "_Cancel".
_ ( " Secret chats can only have one "
2020-05-31 15:55:28 +02:00
" end point. If you accept a secret chat on this device, its messages will not be available anywhere "
" else. If you decline, you can still accept the chat on other devices. " ) ,
2020-07-06 18:41:20 +02:00
0 , account . purpleAccount , NULL , NULL , data , 2 ,
2020-08-19 22:51:21 +02:00
// TRANSLATOR: Dialog option, regarding a secret chat; the alternative is "_Cancel"
_ ( " _Accept " ) , acceptSecretChatCb ,
// TRANSLATOR: Dialog option, regarding a secret chat; the alternative is "_Accept"
_ ( " _Cancel " ) , discardSecretChatCb ) ;
2020-05-31 15:55:28 +02:00
}
}
}
2020-06-06 13:57:36 +02:00
void updateOption ( const td : : td_api : : updateOption & option , TdAccountData & account )
{
if ( ( option . name_ = = " version " ) & & option . value_ & &
( option . value_ - > get_id ( ) = = td : : td_api : : optionValueString : : ID ) )
{
purple_debug_misc ( config : : pluginId , " tdlib version: %s \n " ,
static_cast < const td : : td_api : : optionValueString & > ( * option . value_ ) . value_ . c_str ( ) ) ;
} else if ( ( option . name_ = = " message_caption_length_max " ) & & option . value_ & &
( option . value_ - > get_id ( ) = = td : : td_api : : optionValueInteger : : ID ) )
{
account . options . maxCaptionLength = std : : max ( 0 , static_cast < const td : : td_api : : optionValueInteger & > ( * option . value_ ) . value_ ) ;
} else if ( ( option . name_ = = " message_text_length_max " ) & & option . value_ & &
( option . value_ - > get_id ( ) = = td : : td_api : : optionValueInteger : : ID ) )
{
account . options . maxMessageLength = std : : max ( 0 , static_cast < const td : : td_api : : optionValueInteger & > ( * option . value_ ) . value_ ) ;
} else
purple_debug_misc ( config : : pluginId , " Option update %s \n " , option . name_ . c_str ( ) ) ;
}
2020-06-11 00:04:29 +02:00
void populateGroupChatList ( PurpleRoomlist * roomlist , const std : : vector < const td : : td_api : : chat * > & chats ,
const TdAccountData & account )
{
for ( const td : : td_api : : chat * chat : chats )
if ( account . isGroupChatWithMembership ( * chat ) ) {
PurpleRoomlistRoom * room = purple_roomlist_room_new ( PURPLE_ROOMLIST_ROOMTYPE_ROOM ,
chat - > title_ . c_str ( ) , NULL ) ;
purple_roomlist_room_add_field ( roomlist , room , getPurpleChatName ( * chat ) . c_str ( ) ) ;
int32_t groupId = getBasicGroupId ( * chat ) ;
if ( groupId ) {
const td : : td_api : : basicGroupFullInfo * fullInfo = account . getBasicGroupInfo ( groupId ) ;
if ( fullInfo & & ! fullInfo - > description_ . empty ( ) )
purple_roomlist_room_add_field ( roomlist , room , fullInfo - > description_ . c_str ( ) ) ;
}
groupId = getSupergroupId ( * chat ) ;
if ( groupId ) {
const td : : td_api : : supergroupFullInfo * fullInfo = account . getSupergroupInfo ( groupId ) ;
if ( fullInfo & & ! fullInfo - > description_ . empty ( ) )
purple_roomlist_room_add_field ( roomlist , room , fullInfo - > description_ . c_str ( ) ) ;
}
purple_roomlist_room_add ( roomlist , room ) ;
}
purple_roomlist_set_in_progress ( roomlist , FALSE ) ;
}
2020-06-13 16:01:05 +02:00
AccountThread : : AccountThread ( PurpleAccount * purpleAccount , AccountThread : : Callback callback )
{
m_accountUserName = purple_account_get_username ( purpleAccount ) ;
m_accountProtocolId = purple_account_get_protocol_id ( purpleAccount ) ;
m_callback = callback ;
}
void AccountThread : : threadFunc ( )
{
run ( ) ;
g_idle_add ( & AccountThread : : mainThreadCallback , this ) ;
}
static bool g_singleThread = false ;
void AccountThread : : setSingleThread ( )
{
g_singleThread = true ;
}
2020-08-15 19:48:05 +02:00
bool AccountThread : : isSingleThread ( )
{
return g_singleThread ;
}
2020-06-13 16:01:05 +02:00
void AccountThread : : startThread ( )
{
if ( ! g_singleThread ) {
if ( ! m_thread . joinable ( ) )
m_thread = std : : thread ( std : : bind ( & AccountThread : : threadFunc , this ) ) ;
} else {
run ( ) ;
mainThreadCallback ( this ) ;
}
}
gboolean AccountThread : : mainThreadCallback ( gpointer data )
{
AccountThread * self = static_cast < AccountThread * > ( data ) ;
PurpleAccount * account = purple_accounts_find ( self - > m_accountUserName . c_str ( ) ,
self - > m_accountProtocolId . c_str ( ) ) ;
PurpleTdClient * tdClient = account ? getTdClient ( account ) : nullptr ;
if ( self - > m_thread . joinable ( ) )
self - > m_thread . join ( ) ;
if ( tdClient )
( tdClient - > * ( self - > m_callback ) ) ( self ) ;
return FALSE ; // this idle callback will not be called again
}