2
0
mirror of https://github.com/ars3niy/tdlib-purple synced 2025-08-31 22:15:10 +00:00

Implemented room list (mostly for bitlbee and spectrum)

This commit is contained in:
Arseniy Lartsev
2020-06-11 00:04:29 +02:00
parent 9f7418dff2
commit 230b5bfd9a
14 changed files with 336 additions and 27 deletions

View File

@@ -481,7 +481,7 @@ const td::td_api::chat *TdAccountData::getSupergroupChatByGroup(int32_t groupId)
return nullptr;
}
bool TdAccountData::isGroupChatWithMembership(const td::td_api::chat &chat)
bool TdAccountData::isGroupChatWithMembership(const td::td_api::chat &chat) const
{
int groupId = getBasicGroupId(chat);
if (groupId) {

View File

@@ -218,13 +218,14 @@ public:
std::string getDisplayName(int32_t userId) const;
void getUsersByDisplayName(const char *displayName,
std::vector<const td::td_api::user*> &users);
const td::td_api::basicGroup *getBasicGroup(int32_t groupId) const;
const td::td_api::basicGroupFullInfo *getBasicGroupInfo(int32_t groupId) const;
const td::td_api::supergroup *getSupergroup(int32_t groupId) const;
const td::td_api::supergroupFullInfo *getSupergroupInfo(int32_t groupId) const;
const td::td_api::chat *getBasicGroupChatByGroup(int32_t groupId) const;
const td::td_api::chat *getSupergroupChatByGroup(int32_t groupId) const;
bool isGroupChatWithMembership(const td::td_api::chat &chat);
bool isGroupChatWithMembership(const td::td_api::chat &chat) const;
template<typename ReqType, typename... ArgsType>
void addPendingRequest(ArgsType... args)

View File

@@ -147,7 +147,7 @@ PurpleConversation *getImConversation(PurpleAccount *account, const char *userna
PurpleConvChat *getChatConversation(TdAccountData &account, const td::td_api::chat &chat,
int chatPurpleId)
{
std::string chatName = getChatName(chat);
std::string chatName = getPurpleChatName(chat);
bool newChatCreated = false;
PurpleConversation *conv = purple_find_chat(purple_account_get_connection(account.purpleAccount), chatPurpleId);
if (conv == NULL) {
@@ -199,7 +199,7 @@ PurpleConvChat *getChatConversation(TdAccountData &account, const td::td_api::ch
PurpleConvChat *findChatConversation(PurpleAccount *account, const td::td_api::chat &chat)
{
std::string name = getChatName(chat);
std::string name = getPurpleChatName(chat);
PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
name.c_str(), account);
if (conv)
@@ -296,7 +296,7 @@ static void updateGroupChat(PurpleAccount *purpleAccount, const td::td_api::chat
return;
}
std::string chatName = getChatName(chat);
std::string chatName = getPurpleChatName(chat);
PurpleChat *purpleChat = purple_blist_find_chat(purpleAccount, chatName.c_str());
if (!purpleChat) {
purple_debug_misc(config::pluginId, "Adding new chat for %s %d (%s)\n",
@@ -370,7 +370,7 @@ void updateSupergroupChat(TdAccountData &account, int32_t groupId)
void removeGroupChat(PurpleAccount *purpleAccount, const td::td_api::chat &chat)
{
std::string chatName = getChatName(chat);
std::string chatName = getPurpleChatName(chat);
PurpleChat *purpleChat = purple_blist_find_chat(purpleAccount, chatName.c_str());
if (purpleChat)
@@ -1473,3 +1473,28 @@ void updateOption(const td::td_api::updateOption &option, TdAccountData &account
} else
purple_debug_misc(config::pluginId, "Option update %s\n", option.name_.c_str());
}
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);
}

View File

@@ -87,5 +87,7 @@ std::string makeDocumentDescription(const td::td_api::videoNote *document);
void updateSecretChat(td::td_api::object_ptr<td::td_api::secretChat> secretChat,
TdTransceiver &transceiver, TdAccountData &account);
void updateOption(const td::td_api::updateOption &option, TdAccountData &account);
void populateGroupChatList(PurpleRoomlist *roomlist, const std::vector<const td::td_api::chat *> &chats,
const TdAccountData &account);
#endif

View File

@@ -6,18 +6,23 @@
static const char *_(const char *s) { return s; }
static char idKey[] = "id";
static char chatNameComponent[] = "id";
static char inviteKey[] = "link";
static char nameKey[] = "name";
static char typeKey[] = "type";
const char *getChatNameComponent()
{
return chatNameComponent;
}
GList *getChatJoinInfo()
{
// First entry is the internal chat name used to look up conversations
struct proto_chat_entry *pce;
pce = g_new0 (struct proto_chat_entry, 1);
pce->label = _("Chat ID (don't change):");
pce->identifier = idKey;
pce->identifier = chatNameComponent;
pce->required = FALSE;
GList *info = g_list_append (NULL, pce);
@@ -46,7 +51,7 @@ GList *getChatJoinInfo()
return info;
}
std::string getChatName(const td::td_api::chat &chat)
std::string getPurpleChatName(const td::td_api::chat &chat)
{
return "chat" + std::to_string(chat.id_);
}
@@ -58,13 +63,13 @@ GHashTable *getChatComponents(const td::td_api::chat &chat)
name[sizeof(name)-1] = '\0';
GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
g_hash_table_insert(table, idKey, g_strdup(name));
g_hash_table_insert(table, chatNameComponent, g_strdup(name));
return table;
}
const char *getChatName(GHashTable *components)
{
return (const char *)g_hash_table_lookup(components, idKey);
return (const char *)g_hash_table_lookup(components, chatNameComponent);
}
const char *getChatInviteLink(GHashTable *components)

View File

@@ -9,8 +9,9 @@ static constexpr int
GROUP_TYPE_SUPER = 2,
GROUP_TYPE_CHANNEL = 3;
const char *getChatNameComponent();
GList *getChatJoinInfo();
std::string getChatName(const td::td_api::chat &chat);
std::string getPurpleChatName(const td::td_api::chat &chat);
GHashTable *getChatComponents(const td::td_api::chat &chat);
const char *getChatName(GHashTable *components);

View File

@@ -769,6 +769,12 @@ void PurpleTdClient::updatePurpleChatListAndReportConnected()
}
}
for (PurpleRoomlist *roomlist: m_pendingRoomLists) {
populateGroupChatList(roomlist, chats, m_data);
purple_roomlist_unref(roomlist);
}
m_pendingRoomLists.clear();
// Here we could remove buddies for which no private chat exists, meaning they have been remove
// from the contact list perhaps in another client
@@ -2054,6 +2060,29 @@ void PurpleTdClient::showInviteLink(const std::string& purpleChatName)
showChatNotification(m_data, *chat, "Failed to get invite link, full info not known");
}
void PurpleTdClient::getGroupChatList(PurpleRoomlist *roomlist)
{
GList *fields = NULL;
PurpleRoomlistField *f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "",
getChatNameComponent(), TRUE);
fields = g_list_append (fields, f);
// "description" is hard-coded in bitlbee as possible field for chat topic
f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Description"), "description", FALSE);
fields = g_list_append (fields, f);
purple_roomlist_set_fields (roomlist, fields);
purple_roomlist_set_in_progress(roomlist, TRUE);
if (purple_account_is_connected(m_account)) {
std::vector<const td::td_api::chat *> chats;
m_data.getChats(chats);
populateGroupChatList(roomlist, chats, m_data);
} else {
purple_roomlist_ref(roomlist);
m_pendingRoomLists.push_back(roomlist);
}
}
void PurpleTdClient::removeTempFile(int64_t messageId)
{
std::string path = m_data.extractTempFileUpload(messageId);

View File

@@ -39,6 +39,7 @@ public:
void kickUserFromChat(PurpleConversation *conv, const char *name);
void addUserToChat(int purpleChatId, const char *name);
void showInviteLink(const std::string &purpleChatName);
void getGroupChatList(PurpleRoomlist *roomlist);
void setTwoFactorAuth(const char *oldPassword, const char *newPassword, const char *hint,
const char *email);
@@ -170,6 +171,7 @@ private:
std::vector<int32_t> m_usersForNewPrivateChats;
bool m_isProxyAdded = false;
int64_t m_lastChatOrderOffset = 0;
std::vector<PurpleRoomlist *> m_pendingRoomLists;
td::td_api::object_ptr<td::td_api::proxy> m_addedProxy;
td::td_api::object_ptr<td::td_api::proxies> m_proxies;
};

View File

@@ -212,7 +212,9 @@ static GList *tgprpl_chat_join_info (PurpleConnection *gc)
static GHashTable *tgprpl_chat_info_defaults (PurpleConnection *gc, const char *chat_name)
{
return g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
GHashTable *components = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
g_hash_table_insert(components, (gpointer)getChatNameComponent(), g_strdup(chat_name));
return components;
}
static ITransceiverBackend *g_testBackend = nullptr;
@@ -451,15 +453,11 @@ static void tgprpl_rename_buddy(PurpleConnection *gc, const char *who, const cha
static PurpleRoomlist *tgprpl_roomlist_get_list (PurpleConnection *gc)
{
static PurpleRoomlist *roomlist = NULL; // put it on like protocol_data
PurpleTdClient *tdClient = static_cast<PurpleTdClient *>(purple_connection_get_protocol_data(gc));
PurpleRoomlist *roomlist = purple_roomlist_new(purple_connection_get_account(gc));
if (roomlist)
purple_roomlist_unref (roomlist);
roomlist = purple_roomlist_new (purple_connection_get_account (gc));
purple_roomlist_set_in_progress (roomlist, TRUE);
// blah blah blah
purple_roomlist_set_in_progress (roomlist, FALSE);
if (tdClient)
tdClient->getGroupChatList(roomlist);
return roomlist;
}
@@ -468,6 +466,11 @@ static void tgprpl_roomlist_cancel (PurpleRoomlist *list)
{
}
static char *getRoomlistChatName(PurpleRoomlistRoom *room)
{
return g_strdup((char *)purple_roomlist_room_get_fields(room)->data);
}
static gboolean tgprpl_can_receive_file (PurpleConnection *gc, const char *who)
{
return TRUE;
@@ -615,7 +618,7 @@ static PurplePluginProtocolInfo prpl_info = {
.offline_message = NULL,
.whiteboard_prpl_ops = NULL,
.send_raw = NULL,
.roomlist_room_serialize = NULL,
.roomlist_room_serialize = getRoomlistChatName,
.unregister_user = NULL,
.send_attention = NULL,
.get_attention_types = NULL,

View File

@@ -1208,3 +1208,103 @@ TEST_F(GroupChatTest, GetInviteLink)
g_list_free_full(actions, (GDestroyNotify)purple_menu_action_free);
}
TEST_F(GroupChatTest, Roomlist)
{
pluginInfo().login(account);
PurpleRoomlist *superEarlyRoomlist = pluginInfo().roomlist_get_list(connection);
prpl.verifyEvents(RoomlistInProgressEvent(superEarlyRoomlist, TRUE));
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateWaitTdlibParameters>()));
tgl.verifyRequests({
make_object<disableProxy>(),
make_object<getProxies>(),
make_object<setTdlibParameters>(make_object<tdlibParameters>(
false,
std::string(purple_user_dir()) + G_DIR_SEPARATOR_S +
"tdlib" + G_DIR_SEPARATOR_S + "+" + selfPhoneNumber,
"",
false,
false,
false,
false,
0,
"",
"",
"",
"",
"",
false,
false
))
});
tgl.reply(make_object<ok>());
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateWaitEncryptionKey>(true)));
tgl.verifyRequest(checkDatabaseEncryptionKey(""));
tgl.reply(make_object<ok>());
tgl.update(make_object<updateAuthorizationState>(make_object<authorizationStateReady>()));
tgl.verifyNoRequests();
tgl.update(make_object<updateConnectionState>(make_object<connectionStateConnecting>()));
tgl.verifyNoRequests();
prpl.verifyEvents(
ConnectionSetStateEvent(connection, PURPLE_CONNECTING),
ConnectionUpdateProgressEvent(connection, 1, 3)
);
tgl.update(make_object<updateConnectionState>(make_object<connectionStateUpdating>()));
tgl.verifyNoRequests();
prpl.verifyEvents(ConnectionUpdateProgressEvent(connection, 2, 3));
tgl.update(make_object<updateUser>(makeUser(
selfId,
selfFirstName,
selfLastName,
selfPhoneNumber, // Phone number here without + to make it more interesting
make_object<userStatusOffline>()
)));
tgl.update(make_object<updateBasicGroup>(make_object<basicGroup>(
groupId, 2, make_object<chatMemberStatusMember>(), true, 0
)));
tgl.update(make_object<updateNewChat>(makeChat(
groupChatId, make_object<chatTypeBasicGroup>(groupId), groupChatTitle, nullptr, 0, 0, 0
)));
tgl.update(makeUpdateChatListMain(groupChatId));
tgl.update(make_object<updateConnectionState>(make_object<connectionStateReady>()));
tgl.verifyRequest(getContacts());
tgl.reply(make_object<users>());
PurpleRoomlist *earlyRoomlist = pluginInfo().roomlist_get_list(connection);
prpl.verifyEvents(RoomlistInProgressEvent(earlyRoomlist, TRUE));
tgl.verifyRequest(getChats());
tgl.reply(make_object<chats>(std::vector<int64_t>(1, groupChatId)));
tgl.verifyRequest(getChats());
tgl.reply(make_object<chats>());
prpl.verifyEvents(
ConnectionSetStateEvent(connection, PURPLE_CONNECTED),
AddChatEvent(groupChatPurpleName, groupChatTitle, account, nullptr, nullptr),
RoomlistAddRoomEvent(superEarlyRoomlist, "id", groupChatPurpleName.c_str()),
RoomlistInProgressEvent(superEarlyRoomlist, FALSE),
RoomlistAddRoomEvent(earlyRoomlist, "id", groupChatPurpleName.c_str()),
RoomlistInProgressEvent(earlyRoomlist, FALSE),
AccountSetAliasEvent(account, selfFirstName + " " + selfLastName),
ShowAccountEvent(account)
);
tgl.verifyRequest(getBasicGroupFullInfo(groupId));
purple_roomlist_unref(superEarlyRoomlist);
purple_roomlist_unref(earlyRoomlist);
PurpleRoomlist *onlineRoomlist = pluginInfo().roomlist_get_list(connection);
prpl.verifyEvents(
RoomlistInProgressEvent(onlineRoomlist, TRUE),
RoomlistAddRoomEvent(onlineRoomlist, "id", groupChatPurpleName.c_str()),
RoomlistInProgressEvent(onlineRoomlist, FALSE)
);
purple_roomlist_unref(onlineRoomlist);
}

View File

@@ -1,3 +1,4 @@
#include "libpurple-mock.h"
#include "purple-events.h"
#include <purple.h>
#include <stdarg.h>
@@ -716,15 +717,97 @@ void *purple_request_input(void *handle, const char *title, const char *primary,
PurpleRoomlist *purple_roomlist_new(PurpleAccount *account)
{
return NULL;
PurpleRoomlist *roomlist = new PurpleRoomlist;
roomlist->ref = 1;
roomlist->ui_data = new RoomlistData;
return roomlist;
}
void purple_roomlist_set_in_progress(PurpleRoomlist *list, gboolean in_progress)
{
EVENT(RoomlistInProgressEvent, list, in_progress);
}
void purple_roomlist_ref(PurpleRoomlist *list)
{
list->ref++;
}
void purple_roomlist_unref(PurpleRoomlist *list)
{
ASSERT_NE(0u, list->ref);
list->ref--;
if (list->ref == 0) {
delete static_cast<RoomlistData *>(list->ui_data);
delete list;
}
}
PurpleRoomlistField *purple_roomlist_field_new(PurpleRoomlistFieldType type,
const gchar *label, const gchar *name,
gboolean hidden)
{
PurpleRoomlistField *field = new PurpleRoomlistField;
field->type = type;
field->name = strdup(name);
return field;
}
void purple_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
{
RoomlistData *fieldStore = static_cast<RoomlistData *>(list->ui_data);
for (GList *field = fields; field; field = g_list_next(field)) {
PurpleRoomlistField *f = static_cast<PurpleRoomlistField *>(field->data);
fieldStore->emplace_back(f->type, f->name);
free(f->name);
delete f;
}
g_list_free(fields);
}
struct RealRoom {
std::vector<std::string> fieldValues;
GList firstField = {NULL, NULL, NULL};
};
PurpleRoomlistRoom *purple_roomlist_room_new(PurpleRoomlistRoomType type, const gchar *name,
PurpleRoomlistRoom *parent)
{
PurpleRoomlistRoom *room = reinterpret_cast<PurpleRoomlistRoom *>(new RealRoom);
return room;
}
void purple_roomlist_room_add_field(PurpleRoomlist *list, PurpleRoomlistRoom *notRoom, gconstpointer field)
{
RoomlistData *fieldList = static_cast<RoomlistData *>(list->ui_data);
RealRoom *room = reinterpret_cast<RealRoom *>(notRoom);
size_t index = room->fieldValues.size();
switch (fieldList->at(index).first) {
case PURPLE_ROOMLIST_FIELD_BOOL:
room->fieldValues.push_back(field ? "true" : "false");
break;
case PURPLE_ROOMLIST_FIELD_INT:
room->fieldValues.push_back(std::to_string(GPOINTER_TO_INT(field)));
break;
case PURPLE_ROOMLIST_FIELD_STRING:
room->fieldValues.push_back(static_cast<const char *>(field));
break;
}
}
GList * purple_roomlist_room_get_fields(PurpleRoomlistRoom *notRoom)
{
RealRoom *room = reinterpret_cast<RealRoom *>(notRoom);
if (!room->fieldValues.empty())
room->firstField.data = const_cast<char *>(room->fieldValues[0].c_str());
return &room->firstField;
}
void purple_roomlist_room_add(PurpleRoomlist *list, PurpleRoomlistRoom *notRoom)
{
RealRoom *room = reinterpret_cast<RealRoom *>(notRoom);
EVENT(RoomlistAddRoomEvent, list, room->fieldValues);
delete room;
}
void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data)

View File

@@ -1,6 +1,11 @@
#ifndef _LIBPURPLE_MOCK_H
#define _LIBPURPLE_MOCK_H
#include <glib.h>
#include <vector>
#include <string>
#include <purple.h>
extern "C" {
void setFakeFileSize(const char *path, size_t size);
@@ -9,5 +14,7 @@ guint8 *arrayDup(gpointer data, size_t size);
};
using RoomlistData = std::vector<std::pair<PurpleRoomlistFieldType, std::string>>;
#endif

View File

@@ -1,4 +1,5 @@
#include "purple-events.h"
#include "libpurple-mock.h"
#include <gtest/gtest.h>
PurpleEventReceiver g_purpleEvents;
@@ -269,6 +270,27 @@ static void compare(const XferRemoteCancelEvent &actual, const XferRemoteCancelE
COMPARE(filename);
}
static void compare(const RoomlistInProgressEvent &actual, const RoomlistInProgressEvent &expected)
{
COMPARE(list);
COMPARE(inprogress);
}
static void compare(const RoomlistAddRoomEvent &actual, const RoomlistAddRoomEvent &expected)
{
COMPARE(roomlist);
ASSERT_EQ(nullptr, actual.fieldToCheck);
ASSERT_EQ(nullptr, actual.valueToCheck);
ASSERT_NE(nullptr, expected.fieldToCheck);
ASSERT_NE(nullptr, expected.valueToCheck);
RoomlistData *fieldList = static_cast<RoomlistData *>(actual.roomlist->ui_data);
for (unsigned i = 0; i < fieldList->size(); i++)
if (fieldList->at(i).second == expected.fieldToCheck) {
ASSERT_EQ(std::string(expected.valueToCheck), actual.fieldValues.at(i));
}
}
static void compareEvents(const PurpleEvent &actual, const PurpleEvent &expected)
{
ASSERT_EQ(expected.type, actual.type) << "Unexpected libpurple event " << actual.toString() <<
@@ -316,6 +338,8 @@ static void compareEvents(const PurpleEvent &actual, const PurpleEvent &expected
C(XferEnd)
C(XferLocalCancel)
C(XferRemoteCancel)
C(RoomlistInProgress)
C(RoomlistAddRoom)
default:
ASSERT_TRUE(false) << "Unsupported libpurple event " << actual.toString();
};
@@ -457,6 +481,8 @@ std::string PurpleEvent::toString() const
C(XferLocalCancel)
C(XferRemoteCancel)
C(SetUserPhoto)
C(RoomlistInProgress)
C(RoomlistAddRoom)
}
return std::to_string((unsigned)type);
#undef C

View File

@@ -96,7 +96,9 @@ enum class PurpleEventType: uint8_t {
XferEnd,
XferLocalCancel,
XferRemoteCancel,
SetUserPhoto
SetUserPhoto,
RoomlistInProgress,
RoomlistAddRoom
};
struct PurpleEvent {
@@ -503,4 +505,27 @@ struct SetUserPhotoEvent: PurpleEvent {
}
};
struct RoomlistInProgressEvent: PurpleEvent {
PurpleRoomlist *list;
bool inprogress;
RoomlistInProgressEvent(PurpleRoomlist *list, bool inprogress)
: PurpleEvent(PurpleEventType::RoomlistInProgress), list(list), inprogress(inprogress) {}
};
struct RoomlistAddRoomEvent: PurpleEvent {
PurpleRoomlist *roomlist;
std::vector<std::string> fieldValues;
const char *fieldToCheck = NULL;
const char *valueToCheck = NULL;
RoomlistAddRoomEvent(PurpleRoomlist *roomlist, const std::vector<std::string> &values)
: PurpleEvent(PurpleEventType::RoomlistAddRoom), roomlist(roomlist), fieldValues(values) {}
RoomlistAddRoomEvent(PurpleRoomlist *roomlist, const char *fieldToCheck,
const char *valueToCheck)
: PurpleEvent(PurpleEventType::RoomlistAddRoom), roomlist(roomlist), fieldToCheck(fieldToCheck),
valueToCheck(valueToCheck) {}
};
#endif