diff --git a/include/ircd/m/receipt.h b/include/ircd/m/receipt.h index 8c4cf066a..48f3994c3 100644 --- a/include/ircd/m/receipt.h +++ b/include/ircd/m/receipt.h @@ -14,7 +14,11 @@ namespace ircd::m::receipt { // [GET] - bool read(const id::room &, const id::user &, const event::id::closure &); + bool exists(const id::room &, const id::user &, const id::event &); + bool freshest(const id::room &, const id::user &, const id::event &); + bool ignoring(const m::user &, const id::event &); + bool ignoring(const m::user &, const id::room &); + bool read(const id::room &, const id::user &, const id::event::closure &); id::event read(id::event::buf &out, const id::room &, const id::user &); // [SET] diff --git a/ircd/m.cc b/ircd/m.cc index 1a7b5c3a5..ba40d2999 100644 --- a/ircd/m.cc +++ b/ircd/m.cc @@ -1276,25 +1276,25 @@ ircd::m::receipt::read(const id::room &room_id, } ircd::m::event::id::buf -ircd::m::receipt::read(const id::room &room_id, - const id::user &user_id, - const id::event &event_id, +ircd::m::receipt::read(const room::id &room_id, + const user::id &user_id, + const event::id &event_id, const time_t &ms) { - using prototype = event::id::buf (const id::room &, const id::user &, const id::event &, const time_t &); + using prototype = event::id::buf (const room::id &, const user::id &, const event::id &, const time_t &); static mods::import function { - "client_rooms", "commit__m_receipt_m_read" + "m_receipt", "ircd::m::receipt::read" }; return function(room_id, user_id, event_id, ms); } ircd::m::event::id -ircd::m::receipt::read(id::event::buf &out, - const id::room &room_id, - const id::user &user_id) +ircd::m::receipt::read(event::id::buf &out, + const room::id &room_id, + const user::id &user_id) { const event::id::closure copy{[&out] (const event::id &event_id) @@ -1308,20 +1308,78 @@ ircd::m::receipt::read(id::event::buf &out, } bool -ircd::m::receipt::read(const id::room &room_id, - const id::user &user_id, +ircd::m::receipt::read(const room::id &room_id, + const user::id &user_id, const event::id::closure &closure) { - using prototype = bool (const id::room &, const id::user &, const id::event::closure &); + using prototype = bool (const room::id &, const user::id &, const event::id::closure &); static mods::import function { - "m_receipt", "last_receipt__event_id" + "m_receipt", "ircd::m::receipt::read" }; return function(room_id, user_id, closure); } +bool +ircd::m::receipt::ignoring(const user &user, + const room::id &room_id) +{ + using prototype = bool (const m::user &, const m::room::id &); + + static mods::import function + { + "m_receipt", "ircd::m::receipt::ignoring" + }; + + return function(user, room_id); +} + +bool +ircd::m::receipt::ignoring(const user &user, + const event::id &event_id) +{ + using prototype = bool (const m::user &, const m::event::id &); + + static mods::import function + { + "m_receipt", "ircd::m::receipt::ignoring" + }; + + return function(user, event_id); +} + +bool +ircd::m::receipt::freshest(const room::id &room_id, + const user::id &user_id, + const event::id &event_id) +{ + using prototype = bool (const m::room::id &, const m::user::id &, const m::event::id &); + + static mods::import function + { + "m_receipt", "ircd::m::receipt::freshest" + }; + + return function(room_id, user_id, event_id); +} + +bool +ircd::m::receipt::exists(const room::id &room_id, + const user::id &user_id, + const event::id &event_id) +{ + using prototype = bool (const m::room::id &, const m::user::id &, const m::event::id &); + + static mods::import function + { + "m_receipt", "ircd::m::receipt::exists" + }; + + return function(room_id, user_id, event_id); +} + /////////////////////////////////////////////////////////////////////////////// // // m/typing.h diff --git a/modules/client/rooms/read_markers.cc b/modules/client/rooms/read_markers.cc index f457b2857..f4364e73b 100644 --- a/modules/client/rooms/read_markers.cc +++ b/modules/client/rooms/read_markers.cc @@ -32,14 +32,15 @@ post__read_markers(client &client, m_read?: m_fully_read }; + m::event::id::buf head; if(marker) switch(m::sigil(marker)) { case m::id::EVENT: - m::receipt::read(room_id, request.user_id, marker); + head = marker; break; case m::id::ROOM: - m::receipt::read(room_id, request.user_id, m::head(m::room::id(marker))); + head = m::head(m::room::id(marker)); break; default: log::dwarning @@ -49,6 +50,24 @@ post__read_markers(client &client, }; } + const bool useful + { + head && + + // Check if the marker is more recent than the last marker they sent. + // We currently don't do anything with markers targeting the past + m::receipt::freshest(room_id, request.user_id, head) && + + // Check if the user wants to prevent sending a receipt to the room. + !m::receipt::ignoring(request.user_id, room_id) && + + // Check if the user wants to prevent based on this event's specifics. + !m::receipt::ignoring(request.user_id, head) + }; + + if(useful) + m::receipt::read(room_id, request.user_id, head); + return resource::response { client, http::OK diff --git a/modules/client/rooms/receipt.cc b/modules/client/rooms/receipt.cc index 2cf25213c..fb42b113a 100644 --- a/modules/client/rooms/receipt.cc +++ b/modules/client/rooms/receipt.cc @@ -12,22 +12,6 @@ using namespace ircd; -extern "C" m::event::id::buf -commit__m_receipt_m_read(const m::room::id &, - const m::user::id &, - const m::event::id &, - const time_t &); - -extern "C" bool -exists__m_receipt_m_read(const m::room::id &, - const m::user::id &, - const m::event::id &); - -extern "C" bool -fresher__m_receipt_m_read(const m::room::id &, - const m::user::id &, - const m::event::id &); - resource::response post__receipt(client &client, const resource::request &request, @@ -55,219 +39,24 @@ post__receipt(client &client, url::decode(event_id, request.parv[3]) }; - const auto eid + const bool useful { - m::receipt::read(room_id, request.user_id, event_id) + // Check if event_id is more recent than the last receipt's event_id. + // We currently don't do anything with receipts targeting the past. + m::receipt::freshest(room_id, request.user_id, event_id) && + + // Check if user wants to prevent sending receipts to this room. + !m::receipt::ignoring(request.user_id, room_id) && + + // Check if user wants to prevent based on this event's specifics. + !m::receipt::ignoring(request.user_id, event_id) }; + if(useful) + m::receipt::read(room_id, request.user_id, event_id); + return resource::response { client, http::OK }; } - -/// Does the user wish to not send receipts for events sent by its specific -/// sender? -static bool -user_ignoring_receipts_sender(const m::user::room &user_room, - const m::event::id &event_id) -{ - bool ret{false}; - m::get(std::nothrow, event_id, "sender", [&ret, &user_room] - (const string_view &sender) - { - ret = user_room.has("ircd.read.ignore", sender); - }); - - return ret; -} - -/// Does the user wish to not send receipts for events for this entire room? -static bool -user_ignoring_receipts_room(const m::user::room &user_room, - const m::room::id &room_id) -{ - return user_room.has("ircd.read.ignore", room_id); -} - -m::event::id::buf -commit__m_receipt_m_read(const m::room::id &room_id, - const m::user::id &user_id, - const m::event::id &event_id, - const time_t &ms) -{ - if(!fresher__m_receipt_m_read(room_id, user_id, event_id)) - return {}; - - const m::user::room user_room - { - user_id - }; - - // Check if receipts for the room are disabled first because we have the - // room_id on the stack, whereas the event sender requires column queries - // from the event_id. - const bool ignored - { - user_ignoring_receipts_room(user_room, room_id) || - user_ignoring_receipts_sender(user_room, event_id) - }; - - if(ignored) - { - log::debug - { - "no receipt for %s by %s in %s @ %zd (ircd.read.ignore)", - string_view{event_id}, - string_view{user_id}, - string_view{room_id}, - ms - }; - - return {}; - } - - const auto evid - { - send(user_room, user_id, "ircd.read", room_id, - { - { "event_id", event_id }, - { "ts", ms } - }) - }; - - log::info - { - "%s read by %s in %s @ %zd => %s (local)", - string_view{event_id}, - string_view{user_id}, - string_view{room_id}, - ms, - string_view{evid} - }; - - const json::value event_ids[] - { - { event_id } - }; - - const json::members m_read - { - { "data", - { - { "ts", ms } - }}, - { "event_ids", { event_ids, 1 } }, - }; - - json::iov event, content; - const json::iov::push push[] - { - { event, { "type", "m.receipt" } }, - { event, { "room_id", room_id } }, - { content, { room_id, - { - { "m.read", - { - { user_id, m_read } - }} - }}} - }; - - m::vm::copts opts; - opts.add_hash = false; - opts.add_sig = false; - opts.add_event_id = false; - opts.add_origin = true; - opts.add_origin_server_ts = false; - opts.conforming = false; - return m::vm::eval - { - event, content, opts - }; -} - -bool -fresher__m_receipt_m_read(const m::room::id &room_id, - const m::user::id &user_id, - const m::event::id &event_id) -try -{ - const m::user::room user_room - { - user_id - }; - - bool ret{true}; - user_room.get("ircd.read", room_id, [&ret, &event_id] - (const m::event &event) - { - const auto &content - { - at<"content"_>(event) - }; - - const m::event::id &previous_id - { - unquote(content.get("event_id")) - }; - - if(event_id == previous_id) - { - ret = false; - return; - } - - const m::event::idx &previous_idx - { - index(previous_id) - }; - - const m::event::idx &event_idx - { - index(event_id) - }; - - ret = event_idx > previous_idx; - }); - - return ret; -} -catch(const std::exception &e) -{ - log::derror - { - m::log, "Freshness of receipt in %s from %s for %s :%s", - string_view{room_id}, - string_view{user_id}, - string_view{event_id}, - e.what() - }; - - return true; -} - -bool -exists__m_receipt_m_read(const m::room::id &room_id, - const m::user::id &user_id, - const m::event::id &event_id) -{ - const m::user::room user_room - { - user_id - }; - - bool ret{false}; - user_room.get(std::nothrow, "ircd.read", room_id, [&ret, &event_id] - (const m::event &event) - { - const auto &content - { - at<"content"_>(event) - }; - - ret = unquote(content.get("event_id")) == event_id; - }); - - return ret; -} diff --git a/modules/m_receipt.cc b/modules/m_receipt.cc index 044798921..b5f63296c 100644 --- a/modules/m_receipt.cc +++ b/modules/m_receipt.cc @@ -10,19 +10,18 @@ using namespace ircd; -mapi::header -IRCD_MODULE -{ - "Matrix Receipts" -}; - -extern "C" bool last_receipt__event_id(const m::room::id &, const m::user::id &, const m::event::id::closure &); static void handle_m_receipt_m_read(const m::room::id &, const m::user::id &, const m::event::id &, const time_t &); static void handle_m_receipt_m_read(const m::room::id &, const m::user::id &, const m::edu::m_receipt::m_read &); static void handle_m_receipt_m_read(const m::room::id &, const json::object &); static void handle_m_receipt(const m::room::id &, const json::object &); static void handle_edu_m_receipt(const m::event &, m::vm::eval &); +mapi::header +IRCD_MODULE +{ + "Matrix Receipts" +}; + const m::hookfn _m_receipt_eval { @@ -215,8 +214,81 @@ catch(const std::exception &e) }; } +m::event::id::buf +IRCD_MODULE_EXPORT +ircd::m::receipt::read(const m::room::id &room_id, + const m::user::id &user_id, + const m::event::id &event_id, + const time_t &ms) +{ + const m::user::room user_room + { + user_id + }; + + const auto evid + { + send(user_room, user_id, "ircd.read", room_id, + { + { "event_id", event_id }, + { "ts", ms } + }) + }; + + log::info + { + "%s read by %s in %s @ %zd => %s (local)", + string_view{event_id}, + string_view{user_id}, + string_view{room_id}, + ms, + string_view{evid} + }; + + const json::value event_ids[] + { + { event_id } + }; + + const json::members m_read + { + { "data", + { + { "ts", ms } + }}, + { "event_ids", { event_ids, 1 } }, + }; + + json::iov event, content; + const json::iov::push push[] + { + { event, { "type", "m.receipt" } }, + { event, { "room_id", room_id } }, + { content, { room_id, + { + { "m.read", + { + { user_id, m_read } + }} + }}} + }; + + m::vm::copts opts; + opts.add_hash = false; + opts.add_sig = false; + opts.add_event_id = false; + opts.add_origin = true; + opts.add_origin_server_ts = false; + opts.conforming = false; + return m::vm::eval + { + event, content, opts + }; +} + bool -last_receipt__event_id(const m::room::id &room_id, +IRCD_MODULE_EXPORT +ircd::m::receipt::read(const m::room::id &room_id, const m::user::id &user_id, const m::event::id::closure &closure) { @@ -244,3 +316,119 @@ last_receipt__event_id(const m::room::id &room_id, closure(event_id); }); } + + +/// Does the user wish to not send receipts for events sent by its specific +/// sender? +bool +IRCD_MODULE_EXPORT +ircd::m::receipt::ignoring(const m::user &user, + const m::event::id &event_id) +{ + bool ret{false}; + m::get(std::nothrow, event_id, "sender", [&ret, &user] + (const string_view &sender) + { + const m::user::room user_room{user}; + ret = user_room.has("ircd.read.ignore", sender); + }); + + return ret; +} + +/// Does the user wish to not send receipts for events for this entire room? +bool +IRCD_MODULE_EXPORT +ircd::m::receipt::ignoring(const m::user &user, + const m::room::id &room_id) +{ + const m::user::room user_room{user}; + return user_room.has("ircd.read.ignore", room_id); +} + +bool +IRCD_MODULE_EXPORT +ircd::m::receipt::freshest(const m::room::id &room_id, + const m::user::id &user_id, + const m::event::id &event_id) +try +{ + const m::user::room user_room + { + user_id + }; + + bool ret{true}; + user_room.get("ircd.read", room_id, [&ret, &event_id] + (const m::event &event) + { + const auto &content + { + at<"content"_>(event) + }; + + const m::event::id &previous_id + { + unquote(content.get("event_id")) + }; + + if(event_id == previous_id) + { + ret = false; + return; + } + + const m::event::idx &previous_idx + { + index(previous_id) + }; + + const m::event::idx &event_idx + { + index(event_id) + }; + + ret = event_idx > previous_idx; + }); + + return ret; +} +catch(const std::exception &e) +{ + log::derror + { + m::log, "Freshness of receipt in %s from %s for %s :%s", + string_view{room_id}, + string_view{user_id}, + string_view{event_id}, + e.what() + }; + + return true; +} + +bool +IRCD_MODULE_EXPORT +ircd::m::receipt::exists(const m::room::id &room_id, + const m::user::id &user_id, + const m::event::id &event_id) +{ + const m::user::room user_room + { + user_id + }; + + bool ret{false}; + user_room.get(std::nothrow, "ircd.read", room_id, [&ret, &event_id] + (const m::event &event) + { + const auto &content + { + at<"content"_>(event) + }; + + ret = unquote(content.get("event_id")) == event_id; + }); + + return ret; +}