aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-01 22:21:14 +0200
committerdec05eba <dec05eba@protonmail.com>2020-10-01 22:21:14 +0200
commitef1dd33682ae26b4af1343aaecf443e7cd883674 (patch)
treecf3c959f1cf9f8b6b401d384537685191ae3e4ba /src/plugins
parent30dbaeb2b175c1e67f57aba748ced1a2280fb56d (diff)
Matrix: implement mention/reply notifications
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Matrix.cpp105
1 files changed, 101 insertions, 4 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 6d89ff4..e182c11 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -54,7 +54,7 @@ namespace QuickMedia {
char url[512];
if(next_batch.empty())
- snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0&full_state=true", homeserver.c_str());
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0", homeserver.c_str());
else
snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str());
@@ -312,9 +312,18 @@ namespace QuickMedia {
room_it->second->prev_batch = prev_batch_json.asString();
}
+ // TODO: Is there no better way to check for notifications? this is not robust...
+ bool has_unread_notifications = false;
+ const Json::Value &unread_notification_json = (*it)["unread_notifications"];
+ if(unread_notification_json.isObject()) {
+ const Json::Value &highlight_count_json = unread_notification_json["highlight_count"];
+ if(highlight_count_json.isNumeric() && highlight_count_json.asInt64() > 0)
+ has_unread_notifications = true;
+ }
+
const Json::Value &events_json = timeline_json["events"];
events_add_user_info(events_json, room_it->second.get());
- events_add_messages(events_json, room_it->second.get(), MessageDirection::AFTER, &room_messages);
+ events_add_messages(events_json, room_it->second.get(), MessageDirection::AFTER, &room_messages, has_unread_notifications);
events_set_room_name(events_json, room_it->second.get());
}
@@ -400,13 +409,65 @@ namespace QuickMedia {
return "";
}
- void Matrix::events_add_messages(const Json::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncMessages *room_messages) {
+ // TODO: Is this really the proper way to check for username mentions?
+ static bool is_username_seperating_character(char c) {
+ switch(c) {
+ case ' ':
+ case '\n':
+ case '\t':
+ case '\v':
+ case '.':
+ case ',':
+ case '@':
+ case ':':
+ case '?':
+ case '!':
+ case '<':
+ case '>':
+ case '\0':
+ return true;
+ default:
+ return false;
+ }
+ return false;
+ }
+
+ // TODO: Do not show notification if mention is a reply to somebody else that replies to me? also dont show notification everytime a mention is edited
+ static bool message_contains_user_mention(const std::string &msg, const std::string &username) {
+ if(msg.empty())
+ return false;
+
+ size_t index = 0;
+ while(index < msg.size()) {
+ size_t found_index = msg.find(username, index);
+ if(found_index == std::string::npos)
+ return false;
+
+ char prev_char = ' ';
+ if(found_index > 0)
+ prev_char = msg[found_index - 1];
+
+ char next_char = '\0';
+ if(found_index + username.size() < msg.size() - 1)
+ next_char = msg[found_index + username.size()];
+
+ if(is_username_seperating_character(prev_char) && is_username_seperating_character(next_char))
+ return true;
+
+ index += username.size();
+ }
+
+ return false;
+ }
+
+ void Matrix::events_add_messages(const Json::Value &events_json, RoomData *room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications) {
if(!events_json.isArray())
return;
std::vector<std::shared_ptr<Message>> *room_sync_messages = nullptr;
if(room_messages)
room_sync_messages = &(*room_messages)[room_data];
+
std::vector<std::shared_ptr<Message>> new_messages;
for(const Json::Value &event_item_json : events_json) {
@@ -448,6 +509,11 @@ namespace QuickMedia {
if(!body_json.isString())
continue;
+ time_t timestamp = 0;
+ const Json::Value &origin_server_ts = event_item_json["origin_server_ts"];
+ if(origin_server_ts.isNumeric())
+ timestamp = origin_server_ts.asInt64();
+
std::string replaces_event_id;
const Json::Value &relates_to_json = content_json["m.relates_to"];
if(relates_to_json.isObject()) {
@@ -485,6 +551,10 @@ namespace QuickMedia {
message->event_id = event_id_str;
message->body = body_json.asString();
message->replaces_event_id = std::move(replaces_event_id);
+ // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
+ if(has_unread_notifications && !username.empty())
+ message->mentions_me = message_contains_user_mention(message->body, username) || message_contains_user_mention(message->body, "@room");
+ message->timestamp = timestamp;
new_messages.push_back(message);
room_data->message_by_event_id[event_id_str] = message;
if(room_sync_messages)
@@ -631,7 +701,7 @@ namespace QuickMedia {
events_set_room_name(state_json, room_data);
const Json::Value &chunk_json = json_root["chunk"];
- events_add_messages(chunk_json, room_data, MessageDirection::BEFORE, nullptr);
+ events_add_messages(chunk_json, room_data, MessageDirection::BEFORE, nullptr, false);
const Json::Value &end_json = json_root["end"];
if(!end_json.isString()) {
@@ -1232,6 +1302,7 @@ namespace QuickMedia {
json_root["homeserver"] = homeserver;
this->user_id = user_id_json.asString();
+ this->username = extract_user_name_from_user_id(this->user_id);
this->access_token = access_token_json.asString();
this->homeserver = homeserver;
@@ -1266,6 +1337,7 @@ namespace QuickMedia {
// Make sure all fields are reset here!
room_data_by_id.clear();
user_id.clear();
+ username.clear();
access_token.clear();
homeserver.clear();
next_batch.clear();
@@ -1390,6 +1462,7 @@ namespace QuickMedia {
}
this->user_id = std::move(user_id);
+ this->username = extract_user_name_from_user_id(this->user_id);
this->access_token = std::move(access_token);
this->homeserver = std::move(homeserver);
return PluginResult::OK;
@@ -1440,6 +1513,30 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult Matrix::set_read_marker(const std::string &room_id, const Message *message) {
+ Json::Value request_data(Json::objectValue);
+ request_data["m.fully_read"] = message->event_id;
+ request_data["m.read"] = message->event_id;
+ request_data["m.hidden"] = false; // What is this for? element sends it but its not part of the documentation. Is it for hiding read receipt from other users? in that case, TODO: make it configurable
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", Json::writeString(builder, std::move(request_data)) },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/read_markers", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
+
+ return PluginResult::OK;
+ }
+
bool Matrix::was_message_posted_by_me(const std::string &room_id, void *message) const {
auto room_it = room_data_by_id.find(room_id);
if(room_it == room_data_by_id.end()) {