aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard van der Hoff <github@rvanderhoff.org.uk>2017-05-30 14:49:44 +0100
committerGitHub <noreply@github.com>2017-05-30 14:49:44 +0100
commitbe53d53c4f7e23d5b3532f56c59a3dfb82c9b38b (patch)
tree286aac4127d23e3a9d08db05bc3e5547916a054c
parent4cc8da9456f2169fe48849f53374b21baa7236b9 (diff)
parentc426fb952d8bf985c2dc956db1bede4d35fccc1c (diff)
downloadpurple-matrix-be53d53c4f7e23d5b3532f56c59a3dfb82c9b38b.tar.gz
Merge pull request #38 from EionRobb/typing-notifications3
Typing notifications
-rw-r--r--libmatrix.c22
-rw-r--r--matrix-api.c51
-rw-r--r--matrix-api.h23
-rw-r--r--matrix-room.c90
-rw-r--r--matrix-room.h5
-rw-r--r--matrix-statetable.c6
-rw-r--r--matrix-sync.c11
7 files changed, 206 insertions, 2 deletions
diff --git a/libmatrix.c b/libmatrix.c
index acb188b..e0a1e64 100644
--- a/libmatrix.c
+++ b/libmatrix.c
@@ -76,6 +76,25 @@ static GList *matrixprpl_status_types(PurpleAccount *acct)
}
/**
+ * handle sending typing notifications in a chat
+ */
+static guint matrixprpl_conv_send_typing(PurpleConversation *conv,
+ PurpleTypingState state, gpointer ignored)
+{
+ PurpleConnection *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)
@@ -84,6 +103,9 @@ void matrixprpl_login(PurpleAccount *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);
+
pc->flags |= PURPLE_CONNECTION_HTML;
}
diff --git a/matrix-api.c b/matrix-api.c
index 3d72728..089c446 100644
--- a/matrix-api.c
+++ b/matrix-api.c
@@ -789,6 +789,57 @@ MatrixApiRequestData *matrix_api_join_room(MatrixConnectionData *conn,
return fetch_data;
}
+MatrixApiRequestData *matrix_api_typing(MatrixConnectionData *conn,
+ const gchar *room_id, gboolean typing,
+ gint typing_timeout, MatrixApiCallback callback,
+ MatrixApiErrorCallback error_callback,
+ MatrixApiBadResponseCallback bad_response_callback,
+ gpointer user_data)
+{
+ GString *url;
+ MatrixApiRequestData *fetch_data;
+ JsonNode *body_node;
+ JsonGenerator *generator;
+ gchar *json;
+ JsonObject *content;
+
+ /* 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);
+ content = json_object_new();
+ json_object_set_boolean_member(content, "typing", typing);
+ if (typing == TRUE) {
+ json_object_set_int_member(content, "timeout", typing_timeout);
+ }
+ 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);
+ json_object_unref(content);
+
+ 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 183405c..375e88a 100644
--- a/matrix-api.h
+++ b/matrix-api.h
@@ -236,6 +236,29 @@ 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 typing Whether to mark the user as typing
+ * @param typing_timeout How long the server should mark the user as typing
+ * @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, gboolean typing,
+ gint typing_timeout, 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 9e8f805..4eae41f 100644
--- a/matrix-room.c
+++ b/matrix-room.c
@@ -184,6 +184,77 @@ static void _on_topic_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
@@ -209,6 +280,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);
+ }
else if(strcmp(event_type, "m.room.topic") == 0) {
_on_topic_change(conv, new_state);
}
@@ -1183,6 +1257,22 @@ 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)
+{
+ MatrixConnectionData *acct;
+ PurpleConnection *pc = conv->account->gc;
+
+ acct = purple_connection_get_protocol_data(pc);
+
+ // Don't check callbacks as it's inconsequential whether typing notifications go through
+ matrix_api_typing(acct, conv->name, typing, 25000,
+ NULL, NULL, NULL, NULL);
+
+}
+
+/**
* 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..b0bd396 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(struct _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..84f8be1 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,13 @@ 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 */
+ /* (uses the state table to track the state of who is typing and who isn't) */
+ 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);
}