diff --git a/src/chat/chat_member.c b/src/chat/chat_member.c index 07168adc..13c33e13 100644 --- a/src/chat/chat_member.c +++ b/src/chat/chat_member.c @@ -84,6 +84,8 @@ void chat_core_serialize_member(const struct chat_member_display* src, uint8_t* dst[13] = src->nat_type; dst[14] = src->storage; memcpy(dst + 15, src->name, 64); + mf_put64(dst + 79, src->avatar_server); + memcpy(dst + 87, src->avatar_file, 69); } int chat_core_deserialize_member(const uint8_t* src, struct chat_member_display* dst) { @@ -97,9 +99,26 @@ int chat_core_deserialize_member(const uint8_t* src, struct chat_member_display* dst->storage = src[14]; memcpy(dst->name, src + 15, 64); dst->name[63] = '\0'; + dst->avatar_server = mf_get64(src + 79); + memcpy(dst->avatar_file, src + 87, 69); dst->avatar_file[68] = 0; return 0; } +static void member_avatar(struct chat_member_display* m, const char* userinfo) { + m->avatar_server = 0; memset(m->avatar_file, 0, sizeof(m->avatar_file)); + char server[MS_USERINFO_MAX + 1], file[MS_USERINFO_MAX + 1]; + if (!userinfo || json_flat_get(userinfo, "avatar_server", server, sizeof(server)) < 0 || + json_flat_get(userinfo, "avatar_file", file, sizeof(file)) < 0) return; + if (strlen(server) != 16 || !mf_avatar_file_valid(file)) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "avatar: invalid profile reference node=%016llx", (unsigned long long)m->node_id); return; + } + for (int i = 0; i < 16; i++) if (!((server[i] >= '0' && server[i] <= '9') || (server[i] >= 'a' && server[i] <= 'f'))) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "avatar: invalid server ID node=%016llx", (unsigned long long)m->node_id); return; + } + m->avatar_server = strtoull(server, NULL, 16); + if (m->avatar_server) memcpy(m->avatar_file, file, 69); +} + /* ─── запрос списка мемберов ─── */ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t** out, int* count) { @@ -120,7 +139,7 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin snprintf(sql, sizeof(sql), "SELECT p.node_id, COALESCE(n.name,'')," " COALESCE(p.local_nick,''), COALESCE(p.adm_tags,'')," - " COALESCE(p.storage,0), COALESCE(p.deleted,0)" + " COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,'')" " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id" " ORDER BY p.node_id ASC", peers_tbl); @@ -142,6 +161,7 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin } struct chat_member_display* m = &members[cnt]; m->node_id = (uint64_t)sqlite3_column_int64(st, 0); + member_avatar(m, (const char*)sqlite3_column_text(st, 6)); m->online = (uint8_t)chat_core_member_online(inst, ch_id, m->node_id); const char* node_name = (const char*)sqlite3_column_text(st, 1); const char* local_nick = (const char*)sqlite3_column_text(st, 2); @@ -247,7 +267,7 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u snprintf(sql, sizeof(sql), "SELECT COALESCE(n.name,'')," " COALESCE(p.local_nick,''), COALESCE(p.adm_tags,'')," - " COALESCE(p.storage,0), COALESCE(p.deleted,0)" + " COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,'')" " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id" " WHERE p.node_id=?", peers_tbl); @@ -257,6 +277,7 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u struct chat_member_display m; m.node_id = node_id; + member_avatar(&m, (const char*)sqlite3_column_text(st, 5)); m.online = (uint8_t)chat_core_member_online(inst, ch_id, node_id); const char* node_name = (const char*)sqlite3_column_text(st, 0); const char* local_nick = (const char*)sqlite3_column_text(st, 1); diff --git a/src/chat/chat_member.h b/src/chat/chat_member.h index cacd2b67..b90161f9 100644 --- a/src/chat/chat_member.h +++ b/src/chat/chat_member.h @@ -5,7 +5,7 @@ * Live-снимки для всех потребителей формирует chat_core_get_member_list() в uasync-потоке. * Android дополняет ими свой JSON-список с расширенными свойствами участников. * - * Формат на проводе (ручная сериализация, фиксированный размер 79 байт): + * Формат на проводе (ручная сериализация, фиксированный размер 156 байт): * [0-7] node_id uint64_t LE * [8] online uint8_t * [9] is_self uint8_t @@ -14,6 +14,8 @@ * [13] nat_type uint8_t ADDR_TYPE_* * [14] storage uint8_t * [15-78] name char[64] local_nick если задан, иначе node name. \0-padded + * [79-86] avatar_server uint64_t LE; 0 = нет аватарки + * [87-155] avatar_file char[69], sha256.png с NUL */ #ifndef CHAT_MEMBER_H @@ -32,7 +34,7 @@ extern "C" { #define CHAT_MEMBER_FLAG_DELETED 0x08 #define CHAT_MEMBER_FLAG_RADIO 0x10 -#define CHAT_MEMBER_DISPLAY_SIZE 79 +#define CHAT_MEMBER_DISPLAY_SIZE 156 /* Получить всех мемберов канала. out = u_malloc-массив, count = количество. */ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t** out, int* count); @@ -54,6 +56,8 @@ struct chat_member_display { uint8_t nat_type; uint8_t storage; char name[64]; + uint64_t avatar_server; + char avatar_file[69]; }; void chat_core_serialize_member(const struct chat_member_display* src, uint8_t* dst); diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index dadcffc6..fbeb835a 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -706,6 +706,7 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, static void member_committed(struct UTUN_INSTANCE* inst, const char* ns, uint64_t node_id) { merkle_sync_changed(inst, ns); + _fire_props_changed(inst, node_id, NULL, ns); struct TOPO_GROUP* g = inst->topo_groups ? topo_groups_find(inst->topo_groups, strtoull(ns, NULL, 10)) : NULL; struct ETCP_CONN* conn = instance_find_conn(inst, node_id); if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) {