diff options
-rw-r--r-- | libmatrix.c | 23 | ||||
-rw-r--r-- | matrix-api.c | 44 | ||||
-rw-r--r-- | matrix-api.h | 22 | ||||
-rw-r--r-- | matrix-room.c | 98 | ||||
-rw-r--r-- | matrix-room.h | 5 | ||||
-rw-r--r-- | matrix-statetable.c | 6 | ||||
-rw-r--r-- | matrix-sync.c | 10 |
7 files changed, 206 insertions, 2 deletions
diff --git a/libmatrix.c b/libmatrix.c index 5b3118c..2307494 100644 --- a/libmatrix.c +++ b/libmatrix.c @@ -76,6 +76,26 @@ static GList *matrixprpl_status_types(PurpleAccount *acct) } /** + * handle sending typing notifications in a chat + */ +static guint matrixprpl_conv_send_typing(PurpleConversation *conv, + PurpleTypingState state, PurpleConnection *pc) +{ + if (pc == NULL) + pc = purple_conversation_get_gc(conv); + + if (!PURPLE_CONNECTION_IS_CONNECTED(pc)) + return 0; + + if (g_strcmp0(purple_plugin_get_id(purple_connection_get_prpl(pc)), PRPL_ID)) + return 0; + + matrix_room_send_typing(conv, (state == PURPLE_TYPING)); + + return 20; +} + +/** * Start the connection to a matrix account */ void matrixprpl_login(PurpleAccount *acct) @@ -83,6 +103,9 @@ void matrixprpl_login(PurpleAccount *acct) PurpleConnection *pc = purple_account_get_connection(acct); matrix_connection_new(pc); matrix_connection_start_login(pc); + + purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", + acct, PURPLE_CALLBACK(matrixprpl_conv_send_typing), pc); } diff --git a/matrix-api.c b/matrix-api.c index b72af20..f4f009a 100644 --- a/matrix-api.c +++ b/matrix-api.c @@ -785,6 +785,50 @@ MatrixApiRequestData *matrix_api_join_room(MatrixConnectionData *conn, return fetch_data; } +MatrixApiRequestData *matrix_api_typing(MatrixConnectionData *conn, + const gchar *room_id, JsonObject *content, + MatrixApiCallback callback, + MatrixApiErrorCallback error_callback, + MatrixApiBadResponseCallback bad_response_callback, + gpointer user_data) +{ + GString *url; + MatrixApiRequestData *fetch_data; + JsonNode *body_node; + JsonGenerator *generator; + gchar *json; + + /* purple_url_encode uses a single static buffer, so we have to build up + * the url gradually + */ + url = g_string_new(conn->homeserver); + g_string_append(url, "_matrix/client/r0/rooms/"); + g_string_append(url, purple_url_encode(room_id)); + g_string_append(url, "/typing/"); + g_string_append(url, purple_url_encode(conn->user_id)); + g_string_append(url, "?access_token="); + g_string_append(url, purple_url_encode(conn->access_token)); + + body_node = json_node_new(JSON_NODE_OBJECT); + json_node_set_object(body_node, content); + + generator = json_generator_new(); + json_generator_set_root(generator, body_node); + json = json_generator_to_data(generator, NULL); + g_object_unref(G_OBJECT(generator)); + json_node_free(body_node); + + purple_debug_info("matrixprpl", "typing in %s\n", room_id); + + fetch_data = matrix_api_start(url->str, "PUT", json, conn, callback, + error_callback, bad_response_callback, + user_data, 0); + g_free(json); + g_string_free(url, TRUE); + + return fetch_data; +} + MatrixApiRequestData *matrix_api_leave_room(MatrixConnectionData *conn, const gchar *room_id, diff --git a/matrix-api.h b/matrix-api.h index 8463a93..59f6b27 100644 --- a/matrix-api.h +++ b/matrix-api.h @@ -235,6 +235,28 @@ MatrixApiRequestData *matrix_api_join_room(MatrixConnectionData *conn, MatrixApiBadResponseCallback bad_response_callback, gpointer user_data); +/** + * Sends a typing notifiaction to a room + * + * @param conn The connection with which to make the request + * @param room_id The room id to send the typing notification to + * @param content The content of the typing event + * @param callback Function to be called when the request completes + * @param error_callback Function to be called if there is an error making + * the request. If NULL, matrix_api_error will be + * used. + * @param bad_response_callback Function to be called if the API gives a non-200 + * response. If NULL, matrix_api_bad_response will be + * used. + * @param user_data Opaque data to be passed to the callbacks + */ +MatrixApiRequestData *matrix_api_typing(MatrixConnectionData *conn, + const gchar *room_id, struct _JsonObject *content, + MatrixApiCallback callback, + MatrixApiErrorCallback error_callback, + MatrixApiBadResponseCallback bad_response_callback, + gpointer user_data); + /** * Leave a room diff --git a/matrix-room.c b/matrix-room.c index a0f91de..aae4075 100644 --- a/matrix-room.c +++ b/matrix-room.c @@ -171,6 +171,77 @@ static void _on_member_change(PurpleConversation *conv, /** + * Called when there is a change list of typing userss. + */ +static void _on_typing(PurpleConversation *conv, + MatrixRoomEvent *old_state, MatrixRoomEvent *new_state) +{ + PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); + JsonArray *old_user_ids, *new_user_ids; + PurpleConvChatBuddyFlags cbflags; + guint i, j; + guint old_len, new_len; + MatrixRoomMemberTable *member_table; + + member_table = matrix_room_get_member_table(conv); + + if (old_state != NULL) { + old_user_ids = matrix_json_object_get_array_member(old_state->content, "user_ids"); + old_len = json_array_get_length(old_user_ids); + } else { + old_len = 0; + } + + new_user_ids = matrix_json_object_get_array_member(new_state->content, "user_ids"); + new_len = json_array_get_length(new_user_ids); + + for (i = 0; i < old_len; i++) { + const gchar *user_id = json_array_get_string_element(old_user_ids, i); + MatrixRoomMember *roommember; + const gchar *displayname; + gboolean new_user_found = FALSE; + + for (j = 0; j < new_len; j++) { + const gchar *new_user_id = json_array_get_string_element(new_user_ids, j); + + if (g_strcmp0(user_id, new_user_id)) { + // no change, remove it from the new list + json_array_remove_element(new_user_ids, j); + new_len--; + new_user_found = TRUE; + break; + } + } + + if (new_user_found == FALSE) { + roommember = matrix_roommembers_lookup_member(member_table, user_id); + displayname = matrix_roommember_get_displayname(roommember); + + // in old list, not in new, i.e. stopped typing + cbflags = purple_conv_chat_user_get_flags(chat, displayname); + cbflags &= ~PURPLE_CBFLAGS_TYPING; + purple_conv_chat_user_set_flags(chat, displayname, cbflags); + } + } + + // everything left in new_user_ids is new typing events + for (i = 0; i < new_len; i++) { + const gchar *user_id = json_array_get_string_element(new_user_ids, i); + MatrixRoomMember *roommember; + const gchar *displayname; + + roommember = matrix_roommembers_lookup_member(member_table, user_id); + displayname = matrix_roommember_get_displayname(roommember); + + cbflags = purple_conv_chat_user_get_flags(chat, displayname); + cbflags |= PURPLE_CBFLAGS_TYPING; + purple_conv_chat_user_set_flags(chat, displayname, cbflags); + } + +} + + +/** * Called when there is a state update. * * old_state may be NULL to indicate addition of a state @@ -196,6 +267,9 @@ static void _on_state_update(const gchar *event_type, strcmp(event_type, "m.room.name") == 0) { _schedule_name_update(conv); } + else if(strcmp(event_type, "m.typing") == 0) { + _on_typing(conv, old_state, new_state); + } } void matrix_room_handle_state_event(struct _PurpleConversation *conv, @@ -1163,6 +1237,30 @@ void matrix_room_send_image(PurpleConversation *conv, int imgstore_id, } /** + * Sends a typing notification in a room with a 25s timeout + */ +void matrix_room_send_typing(PurpleConversation *conv, gboolean typing) +{ + JsonObject *content; + MatrixConnectionData *acct; + PurpleConnection *pc = conv->account->gc; + + acct = purple_connection_get_protocol_data(pc); + + content = json_object_new(); + json_object_set_boolean_member(content, "typing", typing); + if (typing == TRUE) { + json_object_set_int_member(content, "timeout", 25000); + } + + // Don't check callbacks as it's inconsequential whether typing notifications go through + matrix_api_typing(acct, conv->name, content, + NULL, NULL, NULL, NULL); + + json_object_unref(content); +} + +/** * Send a message in a room */ void matrix_room_send_message(PurpleConversation *conv, const gchar *message) diff --git a/matrix-room.h b/matrix-room.h index 1b4a34c..ca0420c 100644 --- a/matrix-room.h +++ b/matrix-room.h @@ -75,6 +75,11 @@ void matrix_room_handle_timeline_event(struct _PurpleConversation *conv, JsonObject *json_event_obj); /** + * Sends a typing notification in a room with a 25s timeout + */ +void matrix_room_send_typing(PurpleConversation *conv, gboolean typing); + +/** * Send a message in a room */ void matrix_room_send_message(struct _PurpleConversation *conv, diff --git a/matrix-statetable.c b/matrix-statetable.c index 8021645..07ecf38 100644 --- a/matrix-statetable.c +++ b/matrix-statetable.c @@ -83,6 +83,12 @@ void matrix_statetable_update(MatrixRoomStateEventTable *state_table, json_event_obj, "sender"); json_content_obj = matrix_json_object_get_object_member( json_event_obj, "content"); + + if (g_strcmp0(event_type, "m.typing") == 0) { + // Create a fake key so we can keep track of typing state + state_key = "typing"; + sender = ""; + } if(event_type == NULL || state_key == NULL || sender == NULL || json_content_obj == NULL) { diff --git a/matrix-sync.c b/matrix-sync.c index 2d403ef..89926ab 100644 --- a/matrix-sync.c +++ b/matrix-sync.c @@ -126,8 +126,8 @@ static PurpleChat *_ensure_blist_entry(PurpleAccount *acct, static void matrix_sync_room(const gchar *room_id, JsonObject *room_data, PurpleConnection *pc) { - JsonObject *state_object, *timeline_object; - JsonArray *state_array, *timeline_array; + JsonObject *state_object, *timeline_object, *ephemeral_object; + JsonArray *state_array, *timeline_array, *ephemeral_array; PurpleConversation *conv; gboolean initial_sync = FALSE; @@ -157,6 +157,12 @@ static void matrix_sync_room(const gchar *room_id, timeline_object, "events"); if(timeline_array != NULL) _parse_room_event_array(conv, timeline_array, FALSE); + + /* parse the ephemeral events */ + ephemeral_object = matrix_json_object_get_object_member(room_data, "ephemeral"); + ephemeral_array = matrix_json_object_get_array_member(ephemeral_object, "events"); + if(ephemeral_array != NULL) + _parse_room_event_array(conv, ephemeral_array, TRUE); } |