Sat, 09 Aug 2025 00:19:03 +0800
Another Minor Milestone Reached, Conversation Creation & Recv works now
| .hgignore | file | annotate | diff | comparison | revisions | |
| purplesatoriconnection.c | file | annotate | diff | comparison | revisions | |
| purplesatoriprotocolconversation.c | file | annotate | diff | comparison | revisions | |
| satoriapi.c | file | annotate | diff | comparison | revisions | |
| satoriapi.h | file | annotate | diff | comparison | revisions | |
| satorimessage.h | file | annotate | diff | comparison | revisions | |
| satoritypes.h | file | annotate | diff | comparison | revisions |
--- a/.hgignore Fri Aug 08 09:46:55 2025 +0800 +++ b/.hgignore Sat Aug 09 00:19:03 2025 +0800 @@ -39,4 +39,4 @@ .DS_Store Thumbs.db -.cache/ \ No newline at end of file +.cache/
--- a/purplesatoriconnection.c Fri Aug 08 09:46:55 2025 +0800 +++ b/purplesatoriconnection.c Sat Aug 09 00:19:03 2025 +0800 @@ -27,6 +27,7 @@ #include <libsoup/soup-message.h> #include <libsoup/soup-types.h> #include <libsoup/soup-websocket-connection.h> +#include <time.h> #include "purplesatoriconnection.h" #include "purplesatoriprotocolcontacts.h" @@ -46,6 +47,43 @@ PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {}); /****************************************************************************** + * PurpleConversation Helpers + *****************************************************************************/ + +static void +purple_satori_handle_pending_message(PurpleSatoriConnection *con, + SatoriUser *user, + SatoriChannel *chan, + JsonObject *msg_obj) +{ + PurpleConversation *conversation = \ + purple_satori_add_conversation_from_chan(con, chan); + PurpleConversationMember *mbr = \ + purple_satori_add_conversation_member_from_user( + con, conversation, user); + + const gchar *id = json_object_get_string_member_with_default( + msg_obj, "id", NULL); + const gchar *text = json_object_get_string_member_with_default( + msg_obj, "content", "Invalid Message"); + + time_t created_at = json_object_get_int_member_with_default( + msg_obj, "created_at", 0) / 1000; /* timestamp in mS */ + + PurpleMessage *message = purple_message_new(mbr, text); + if (id) purple_message_set_id(message, id); + + if (created_at) { + GDateTime *ts = g_date_time_new_from_unix_local(created_at); + purple_message_set_timestamp(message, ts); + g_date_time_unref(ts); + } else purple_message_set_timestamp_now(message); + + purple_conversation_write_message(conversation, message); + g_clear_object(&message); +} + +/****************************************************************************** * PurpleConnection WS Callbacks *****************************************************************************/ @@ -107,6 +145,39 @@ ? user.nick : user.name); satori_refresh_buddy_contacts(con, NULL); + satori_refresh_conversations(con, NULL); + break; + } + + case SATORI_WEBSOCKET_OP_EVENT: + { + const gchar *type = json_object_get_string_member(body, "type"); + if (purple_strequal(type, "message-created")) { + JsonObject *obj = json_object_get_object_member( + body, "message"); + + JsonObject *usr_obj = json_object_get_object_member( + json_object_get_object_member(body, "member"), + "user"); + + if (!usr_obj) + usr_obj = \ + json_object_get_object_member(body, "user"); + + if (!usr_obj) break; + + JsonObject *chan_obj = \ + json_object_get_object_member(body, "channel"); + + if (!chan_obj) break; + + SatoriChannel chan; + SatoriUser usr; + satori_channel_from_json(chan_obj, &chan); + satori_user_from_json(usr_obj, &usr); + + purple_satori_handle_pending_message(con, &usr, &chan, obj); + } break; } @@ -115,8 +186,6 @@ } - g_print("op = %d\n", op); - g_object_unref(parser); }
--- a/purplesatoriprotocolconversation.c Fri Aug 08 09:46:55 2025 +0800 +++ b/purplesatoriprotocolconversation.c Sat Aug 09 00:19:03 2025 +0800 @@ -20,8 +20,11 @@ #include "purplesatoriprotocolconversation.h" +#include "purplesatoriconnection.h" #include "purplesatoriplugin.h" #include "purplesatoriprotocol.h" +#include "satoriapi.h" +#include "satoritypes.h" typedef struct { PurpleConversation *conversation; @@ -38,60 +41,6 @@ g_free(info); } -static gint -purple_satori_protocol_contact_sort(gconstpointer a, gconstpointer b, - G_GNUC_UNUSED gpointer data) -{ - return purple_contact_info_compare(PURPLE_CONTACT_INFO((gpointer)a), - PURPLE_CONTACT_INFO((gpointer)b)); -} - -static char * -purple_satori_protocol_generate_conversation_id(PurpleAccount *account, - PurpleCreateConversationDetails *details) -{ - GChecksum *checksum = NULL; - GListModel *participants = NULL; - GListStore *sorted = NULL; - char *ret = NULL; - const char *id = NULL; - - /* Sort the participants. */ - sorted = g_list_store_new(PURPLE_TYPE_CONTACT); - participants = purple_create_conversation_details_get_participants(details); - for(guint i = 0; i < g_list_model_get_n_items(participants); i++) { - PurpleContactInfo *info = NULL; - - info = g_list_model_get_item(participants, i); - g_list_store_insert_sorted(sorted, info, - purple_satori_protocol_contact_sort, - NULL); - g_clear_object(&info); - } - - /* Build a checksum of the account and the sorted participants. */ - checksum = g_checksum_new(G_CHECKSUM_SHA256); - - id = purple_account_get_id(account); - g_checksum_update(checksum, (guchar *)id, -1); - - for(guint i = 0; i < g_list_model_get_n_items(G_LIST_MODEL(sorted)); i++) { - PurpleContactInfo *info = NULL; - - info = g_list_model_get_item(G_LIST_MODEL(sorted), i); - id = purple_contact_info_get_id(info); - g_checksum_update(checksum, (guchar *)id, -1); - g_clear_object(&info); - } - - ret = g_strdup(g_checksum_get_string(checksum)); - - g_clear_pointer(&checksum, g_checksum_free); - g_clear_object(&sorted); - - return ret; -} - /****************************************************************************** * Callbacks *****************************************************************************/ @@ -109,26 +58,22 @@ *****************************************************************************/ static PurpleCreateConversationDetails * purple_satori_protocol_get_create_conversation_details(G_GNUC_UNUSED PurpleProtocolConversation *protocol, - G_GNUC_UNUSED PurpleAccount *account) + G_GNUC_UNUSED PurpleAccount *account) { - return purple_create_conversation_details_new(9); + return purple_create_conversation_details_new(0); } static void purple_satori_protocol_create_conversation_async(PurpleProtocolConversation *protocol, - PurpleAccount *account, - PurpleCreateConversationDetails *details, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer data) + PurpleAccount *account, + PurpleCreateConversationDetails *details, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) { - PurpleConversation *conversation = NULL; - PurpleConversationManager *manager = NULL; - PurpleConversationType type = PURPLE_CONVERSATION_TYPE_UNSET; - GListModel *participants = NULL; - GTask *task = NULL; - char *id = NULL; - guint n_participants = 0; + GListModel *participants = NULL; + GTask *task = NULL; + guint n_participants = 0; task = g_task_new(protocol, cancellable, callback, data); g_task_set_source_tag(task, @@ -136,88 +81,36 @@ participants = purple_create_conversation_details_get_participants(details); n_participants = g_list_model_get_n_items(participants); - if(n_participants == 0) { + if (n_participants == 0) { g_task_return_new_error_literal(task, PURPLE_SATORI_DOMAIN, 0, _("no participants were provided")); g_clear_object(&task); - return; } - if(n_participants == 1) { - type = PURPLE_CONVERSATION_TYPE_DM; - } else { - type = PURPLE_CONVERSATION_TYPE_GROUP_DM; + if (n_participants > 1) { + g_task_return_new_error_literal(task, PURPLE_SATORI_DOMAIN, 0, + _("only dm conversation supported")); + g_clear_object(&task); + return; /* not implimented */ } - id = purple_satori_protocol_generate_conversation_id(account, details); - - conversation = g_object_new( - PURPLE_TYPE_CONVERSATION, - "account", account, - "id", id, - "type", type, - "online", TRUE, - NULL); - g_clear_pointer(&id, g_free); for(guint i = 0; i < g_list_model_get_n_items(participants); i++) { PurpleContactInfo *info = NULL; - PurpleConversationMember *member = NULL; - PurpleConversationMembers *members = NULL; - PurpleTags *tags = NULL; - const char *badge_id = NULL; + SatoriUser user = { 0 }; info = g_list_model_get_item(participants, i); - members = purple_conversation_get_members(conversation); - member = purple_conversation_members_add_member(members, info, FALSE, - NULL); - - tags = purple_contact_info_get_tags(info); - badge_id = purple_tags_get(tags, "satori-badge"); - if(!purple_strempty(badge_id)) { - PurpleBadge *badge = NULL; - PurpleBadgeManager *manager = NULL; - PurpleBadges *badges = NULL; - - badges = purple_conversation_member_get_badges(member); + satori_user_from_contactinfo(info, &user); - manager = purple_badge_manager_get_default(); - badge = purple_badge_manager_find(manager, badge_id); - if(PURPLE_IS_BADGE(badge)) { - purple_badges_add_badge(badges, badge); - } else { - char *icon_name = NULL; - char *id = NULL; - - id = g_strdup_printf("satori-badge-%s", badge_id); - icon_name = g_strdup_printf("satori-badge-%s", badge_id); - badge = purple_badge_new(id, 0, icon_name, " "); - purple_badge_set_description(badge, badge_id); - g_free(id); - g_free(icon_name); - - purple_badge_manager_add(manager, badge); - purple_badges_add_badge(badges, badge); - g_clear_object(&badge); - } - } + satori_create_dm_channel( + PURPLE_SATORI_CONNECTION( + purple_account_get_connection(account)), + &user, task); g_clear_object(&info); - } - g_clear_object(&details); - - manager = purple_conversation_manager_get_default(); - if(!purple_conversation_manager_add(manager, conversation)) { - g_task_return_new_error(task, PURPLE_SATORI_DOMAIN, 0, - _("This conversation already exists.")); - g_clear_object(&task); - + g_clear_object(&details); return; } - - g_task_return_pointer(task, conversation, g_object_unref); - - g_clear_object(&task); } static PurpleConversation *
--- a/satoriapi.c Fri Aug 08 09:46:55 2025 +0800 +++ b/satoriapi.c Sat Aug 09 00:19:03 2025 +0800 @@ -23,10 +23,171 @@ #include <libsoup/soup-message.h> #include <libsoup/soup-session.h> +#include "purplesatoriconnection.h" +#include "purplesatoriplugin.h" +#include "satorimessage.h" +#include "satoritypes.h" +#include "satoriapi.h" -#include "purplesatoriconnection.h" -#include "satorimessage.h" -#include "satoriapi.h" +/****************************************************************************** + * Purple Integration Helpers + *****************************************************************************/ + +PurpleConversationType +satoir_channel_type_to_conversation_type(SatoriChannelType t) +{ + switch (t) { + case SATORI_CHANNEL_DIRECT: + return PURPLE_CONVERSATION_TYPE_DM; + case SATORI_CHANNEL_TEXT: + return PURPLE_CONVERSATION_TYPE_CHANNEL; + case SATORI_CHANNEL_VOICE: + case SATORI_CHANNEL_CATEGORY: + return 0; /* Unsupported */ + default: + return 0; + } +} + +void +purple_satori_add_person_from_user(PurpleSatoriConnection *con, SatoriUser *user) +{ + PurpleAccount *acc = \ + purple_connection_get_account(PURPLE_CONNECTION(con)); + PurpleContactManager *manager = purple_contact_manager_get_default(); + + PurpleContact *contact = NULL; + PurpleContactInfo *info = NULL; + PurplePresence *presence = NULL; + PurplePerson *person = NULL; + + gboolean new_contact = FALSE, new_person = FALSE; + + contact = purple_contact_manager_find_with_id(manager, acc, user->id); + if (!PURPLE_IS_CONTACT(contact)) { + contact = purple_contact_new(acc, user->id); + new_contact = TRUE; + } + + /* Initialize PurpleContactInfo */ + info = PURPLE_CONTACT_INFO(contact); + purple_contact_info_set_display_name(info, user->nick ? + user->nick : user->name); + + /* Initialize PurplePerson */ + person = purple_contact_info_get_person(info); + if (!PURPLE_IS_PERSON(person)) { + person = g_object_new(PURPLE_TYPE_PERSON, + "id", user->id, NULL); + new_person = TRUE; + } + + if (new_person) { + purple_person_add_contact_info(person, info); + purple_contact_info_set_person(info, person); + g_clear_object(&person); + } + + /* Initialize PurplePresence */ + presence = purple_contact_info_get_presence(info); + purple_presence_set_primitive(presence, + PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); + + if (new_contact) { + purple_contact_manager_add(manager, contact); + g_clear_object(&contact); + } +} + +PurpleConversation * +purple_satori_add_conversation_from_chan(PurpleSatoriConnection *con, + SatoriChannel *chan) +{ + PurpleAccount *acc = \ + purple_connection_get_account(PURPLE_CONNECTION(con)); + PurpleConversationManager *manager = \ + purple_conversation_manager_get_default(); + PurpleConversation *conversation = NULL; + + conversation = purple_conversation_manager_find_with_id(manager, acc, + chan->id); + + if (!PURPLE_IS_CONVERSATION(conversation)) { + PurpleConversationType type = \ + satoir_channel_type_to_conversation_type(chan->type); + if (!type) return NULL; + + conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + "account", acc, + "id", chan->id, + "title", chan->name ? chan->name : "Unnamed Channel", + "type", type, + "online", TRUE, + NULL); + + purple_conversation_manager_add(manager, conversation); + g_object_unref(conversation); + } + + purple_conversation_set_online(conversation, TRUE); + return conversation; +} + +PurpleConversationMember * +purple_satori_add_conversation_member_from_user(PurpleSatoriConnection *con, + PurpleConversation *conversation, + SatoriUser *user) +{ + PurpleAccount *acc = \ + purple_connection_get_account(PURPLE_CONNECTION(con)); + PurpleContactManager *manager = \ + purple_contact_manager_get_default(); + PurpleContact *contact = NULL; + + gboolean found = TRUE; + contact = purple_contact_manager_find_with_id(manager, acc, user->id); + + if (!PURPLE_IS_CONTACT(contact)) { + contact = purple_contact_new(acc, user->id); + found = FALSE; + } + + purple_contact_info_set_display_name( + PURPLE_CONTACT_INFO(contact), + user->nick ? user->nick : user->name); + + PurplePresence *presence = purple_contact_info_get_presence( + PURPLE_CONTACT_INFO(contact)); + purple_presence_set_primitive(presence, + PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); + + if (!found) + purple_contact_manager_add(manager, contact); + + PurpleConversationMembers *existing_members = \ + purple_conversation_get_members(conversation); + PurpleConversationMembers *new_members = NULL; + PurpleConversationMember *member = NULL; + + member = purple_conversation_members_find_member( + existing_members, PURPLE_CONTACT_INFO(contact)); + + if (!PURPLE_IS_CONVERSATION_MEMBER(member)) { + new_members = purple_conversation_members_new(); + member = purple_conversation_members_add_member( + new_members, PURPLE_CONTACT_INFO(contact), FALSE, NULL); + + purple_conversation_members_extend(existing_members, new_members); + } + + g_clear_object(&contact); + return member; +} + +/****************************************************************************** + * Buddy/Friend List Synchronization + *****************************************************************************/ static void satori_on_buddy_contacts_resp(SoupSession *session, @@ -62,59 +223,13 @@ if (!data) goto finish; - PurpleAccount *acc = \ - purple_connection_get_account(PURPLE_CONNECTION(con)); - PurpleContactManager *manager = \ - purple_contact_manager_get_default(); - for (guint i = 0; i < json_array_get_length(data); i++) { JsonObject *user_obj = json_array_get_object_element(data, i); SatoriUser user = { 0 }; satori_user_from_json(user_obj, &user); - PurpleContact *contact = NULL; - PurpleContactInfo *info = NULL; - PurplePresence *presence = NULL; - PurplePerson *person = NULL; - - gboolean new_contact = FALSE, new_person = FALSE; - - contact = purple_contact_manager_find_with_id(manager, acc, user.id); - if (!PURPLE_IS_CONTACT(contact)) { - contact = purple_contact_new(acc, user.id); - new_contact = TRUE; - } - - /* Initialize PurpleContactInfo */ - info = PURPLE_CONTACT_INFO(contact); - purple_contact_info_set_username(info, user.id); - purple_contact_info_set_display_name(info, user.nick ? - user.nick : user.name); - - /* Initialize PurplePerson */ - person = purple_contact_info_get_person(info); - if (!PURPLE_IS_PERSON(person)) { - person = g_object_new(PURPLE_TYPE_PERSON, - "id", user.id, NULL); - new_person = TRUE; - } - - if (new_person) { - purple_person_add_contact_info(person, info); - purple_contact_info_set_person(info, person); - g_clear_object(&person); - } - - /* Initialize PurplePresence */ - presence = purple_contact_info_get_presence(info); - purple_presence_set_primitive(presence, - PURPLE_PRESENCE_PRIMITIVE_AVAILABLE); - - if (new_contact) { - purple_contact_manager_add(manager, contact); - g_clear_object(&contact); - } + purple_satori_add_person_from_user(con, &user); } finish: @@ -149,3 +264,254 @@ g_object_unref(msg); g_bytes_unref(data); } + +/****************************************************************************** + * Group/Room List (Recursive) Synchronization + *****************************************************************************/ + +static void satori_refresh_guild_channels(PurpleSatoriConnection *, + const gchar *, + const gchar *); + +static void +satori_on_guild_channels_resp(SoupSession *session, + GAsyncResult *res, + PurpleSatoriConnection *con) +{ + GError *error = NULL; + GBytes *resp = soup_session_send_and_read_finish(session, res, &error); + + if (error) { + purple_debug_error("satori", + "refresh_conversations failed: %s", + error->message); + if (resp) + g_bytes_unref(resp); + g_error_free(error); + return; + } + + gsize sz; + const gchar *ptr = g_bytes_get_data(resp, &sz); + + JsonParser *parser = json_parser_new(); + if (!json_parser_load_from_data(parser, ptr, sz, NULL)) { + purple_debug_warning("satori", "bad json received from ws"); + goto finish; + } + + JsonObject *root = json_node_get_object(json_parser_get_root(parser)); + JsonArray *data = json_object_get_array_member(root, "data"); + + if (!data) goto finish; + + for (guint i = 0; i < json_array_get_length(data); i++) { + JsonObject *chan_obj = json_array_get_object_element(data, i); + + SatoriChannel chan = { 0 }; + satori_channel_from_json(chan_obj, &chan); + + purple_satori_add_conversation_from_chan(con, &chan); + } + +finish: + /* if (next) */ + /* satori_refresh_guild_channels(con, next); */ + + g_bytes_unref(resp); + g_object_unref(parser); +} + +static void +satori_refresh_guild_channels(PurpleSatoriConnection *con, + const gchar *guild_id, + const gchar *next) +{ + GBytes *data = NULL; + + { + JB_BEGIN_OBJ(b); + JBA(b, "guild_id", guild_id); + if (next) + JBA(b, "next", next); + JB_END_OBJ(data, b); + } + + SoupMessage *msg = satori_message_new( + "POST", SATORI_ENDPOINT("/v1/channel.list")); + soup_message_set_request_body_from_bytes(msg, "application/json", data); + + purple_satori_connection_send_and_read_async( + con, msg, 0, NULL, + (GAsyncReadyCallback) satori_on_guild_channels_resp, + PURPLE_SATORI_CONNECTION(con)); + + g_object_unref(msg); + g_bytes_unref(data); +} + +static void +satori_on_guild_list_resp(SoupSession *session, + GAsyncResult *res, + PurpleSatoriConnection *con) +{ + GError *error = NULL; + GBytes *resp = soup_session_send_and_read_finish(session, res, &error); + + if (error) { + purple_debug_error("satori", + "refresh_conversations failed: %s", + error->message); + if (resp) + g_bytes_unref(resp); + g_error_free(error); + return; + } + + gsize sz; + const gchar *ptr = g_bytes_get_data(resp, &sz), *next = NULL; + + JsonParser *parser = json_parser_new(); + if (!json_parser_load_from_data(parser, ptr, sz, NULL)) { + purple_debug_warning("satori", "bad json received from ws"); + goto finish; + } + + JsonObject *root = json_node_get_object(json_parser_get_root(parser)); + JsonArray *data = json_object_get_array_member(root, "data"); + next = json_object_get_string_member_with_default( + root, "next", NULL); + + if (!data) goto finish; + + for (guint i = 0; i < json_array_get_length(data); i++) { + JsonObject *guild_obj = json_array_get_object_element(data, i); + satori_refresh_guild_channels( + con, + json_object_get_string_member(guild_obj, "id"), + NULL); + } + +finish: + if (next) + satori_refresh_conversations(con, next); + + g_bytes_unref(resp); + g_object_unref(parser); +} + +void +satori_refresh_conversations(PurpleSatoriConnection *con, const gchar *next) +{ + GBytes *data = NULL; + + { + JB_BEGIN_OBJ(b); + if (next) + JBA(b, "next", next); + JB_END_OBJ(data, b); + } + + SoupMessage *msg = satori_message_new( + "POST", SATORI_ENDPOINT("/v1/guild.list")); + soup_message_set_request_body_from_bytes(msg, "application/json", data); + + purple_satori_connection_send_and_read_async( + con, msg, 0, NULL, + (GAsyncReadyCallback) satori_on_guild_list_resp, + PURPLE_SATORI_CONNECTION(con)); + + g_object_unref(msg); + g_bytes_unref(data); +} + +typedef struct { + PurpleSatoriConnection *con; + SatoriUser user; + GTask *task; +} SatoriOnDmChannelData; + +static void +satori_on_dm_channel(SoupSession *session, + GAsyncResult *res, + SatoriOnDmChannelData *dptr) +{ + GError *error = NULL; + GBytes *resp = soup_session_send_and_read_finish(session, res, &error); + + if (error) { + purple_debug_error("satori", + "create_dm_channel failed: %s", + error->message); + if (resp) + g_bytes_unref(resp); + + g_task_return_error(dptr->task, error); + goto cleanup; + } + + gsize sz; + const gchar *ptr = g_bytes_get_data(resp, &sz); + + JsonParser *parser = json_parser_new(); + if (!json_parser_load_from_data(parser, ptr, sz, NULL)) { + purple_debug_warning("satori", "bad json received from api"); + g_task_return_new_error_literal(dptr->task, PURPLE_SATORI_DOMAIN, 0, + "bad json received from api"); + goto cleanup; + } + + JsonObject *root = json_node_get_object(json_parser_get_root(parser)); + + SatoriChannel chan; + satori_channel_from_json(root, &chan); + + PurpleConversation *conversation = \ + purple_satori_add_conversation_from_chan(dptr->con, &chan); + + if (!chan.name) + purple_conversation_set_title(conversation, dptr->user.name); + + purple_satori_add_conversation_member_from_user(dptr->con, conversation, + &dptr->user); + + g_task_return_pointer(dptr->task, conversation, g_object_unref); + g_object_unref(parser); +cleanup: + g_clear_object(&dptr->task); + g_free((gchar *) dptr->user.name); + g_free((gchar *) dptr->user.id); + g_free(dptr); +} + +void +satori_create_dm_channel(PurpleSatoriConnection *con, + SatoriUser *user, + GTask *task) +{ + GBytes *data = NULL; + + { + JB_BEGIN_OBJ(b); + JBA(b, "user_id", user->id); + JB_END_OBJ(data, b); + } + + SoupMessage *msg = satori_message_new( + "POST", SATORI_ENDPOINT("/v1/user.channel.create")); + soup_message_set_request_body_from_bytes(msg, "application/json", data); + + SatoriOnDmChannelData *dptr = g_new0(SatoriOnDmChannelData, 1); + dptr->user.name = dptr->user.nick = g_strdup(user->name); + dptr->user.id = g_strdup(user->id); + dptr->task = task; + dptr->con = con; + + purple_satori_connection_send_and_read_async( + con, msg, 0, NULL, + (GAsyncReadyCallback) satori_on_dm_channel, + dptr); + + g_object_unref(msg); + g_bytes_unref(data); +}
--- a/satoriapi.h Fri Aug 08 09:46:55 2025 +0800 +++ b/satoriapi.h Sat Aug 09 00:19:03 2025 +0800 @@ -19,8 +19,16 @@ #ifndef SATORI_API_H #define SATORI_API_H +#include "satoritypes.h" #include "purplesatoriconnection.h" +PurpleConversationType satoir_channel_type_to_conversation_type(SatoriChannelType); +void purple_satori_add_person_from_user(PurpleSatoriConnection *con, SatoriUser *user); +PurpleConversation *purple_satori_add_conversation_from_chan(PurpleSatoriConnection *con, SatoriChannel *chan); +PurpleConversationMember *purple_satori_add_conversation_member_from_user(PurpleSatoriConnection *con, PurpleConversation *conversation, SatoriUser *user); + void satori_refresh_buddy_contacts(PurpleSatoriConnection *con, const gchar *next); +void satori_refresh_conversations(PurpleSatoriConnection *con, const gchar *next); +void satori_create_dm_channel(PurpleSatoriConnection *con, SatoriUser *user, GTask *task); #endif /* SATORI_API_H */
--- a/satorimessage.h Fri Aug 08 09:46:55 2025 +0800 +++ b/satorimessage.h Sat Aug 09 00:19:03 2025 +0800 @@ -97,17 +97,4 @@ return msg; } -static inline void -satori_user_from_json(JsonObject *user_obj, SatoriUser *out_user) { - out_user->id = json_object_get_string_member(user_obj, "id"); - out_user->name = json_object_get_string_member_with_default( - user_obj, "name", NULL); - out_user->nick = json_object_get_string_member_with_default( - user_obj, "nick", NULL); - out_user->avatar = json_object_get_string_member_with_default( - user_obj, "avatar", NULL); - out_user->is_bot = json_object_get_boolean_member_with_default( - user_obj, "is_bot", FALSE); -} - #endif /* SATORI_MESSAGE_H */
--- a/satoritypes.h Fri Aug 08 09:46:55 2025 +0800 +++ b/satoritypes.h Sat Aug 09 00:19:03 2025 +0800 @@ -19,7 +19,9 @@ #ifndef SATORI_TYPES_H #define SATORI_TYPES_H +#include "purplesatoriprotocolconversation.h" #include <glib.h> +#include <json-glib/json-glib.h> typedef enum { SATORI_WEBSOCKET_OP_EVENT = 0, @@ -30,12 +32,59 @@ SATORI_WEBSOCKET_OP_META, } SatoriWebsocketOpcode; -typedef struct { - const gchar *id; - const gchar *name; - const gchar *nick; - const gchar *avatar; - gboolean is_bot; +typedef struct satori_user { + const gchar *id; + const gchar *name; + const gchar *nick; + const gchar *avatar; + gboolean is_bot; } SatoriUser; +typedef enum { + SATORI_CHANNEL_TEXT = 0, + SATORI_CHANNEL_DIRECT, + SATORI_CHANNEL_CATEGORY, + SATORI_CHANNEL_VOICE, +} SatoriChannelType; + +static inline void +satori_user_from_json(JsonObject *user_obj, SatoriUser *out_user) { + out_user->id = json_object_get_string_member(user_obj, "id"); + out_user->name = json_object_get_string_member_with_default( + user_obj, "name", NULL); + out_user->nick = json_object_get_string_member_with_default( + user_obj, "nick", NULL); + out_user->avatar = json_object_get_string_member_with_default( + user_obj, "avatar", NULL); + out_user->is_bot = json_object_get_boolean_member_with_default( + user_obj, "is_bot", FALSE); +} + +static inline void +satori_user_from_contactinfo(PurpleContactInfo *info, SatoriUser *out_user) { + out_user->nick = out_user->name = \ + purple_contact_info_get_display_name(info); + out_user->id = \ + purple_contact_info_get_id(info); +} + +typedef struct satori_channel { + const gchar *id; + SatoriChannelType type; + const gchar *name; + const gchar *parent_id; +} SatoriChannel; + +static inline void +satori_channel_from_json(JsonObject *obj, SatoriChannel *out_chan) { + out_chan->id = json_object_get_string_member(obj, "id"); + out_chan->type = (SatoriChannelType) json_object_get_int_member( + obj, "type"); + out_chan->name = json_object_get_string_member_with_default( + obj, "name", NULL); + out_chan->parent_id = json_object_get_string_member_with_default( + obj, "parent_id", NULL); +} + + #endif /* SATORI_TYPES_H */