From 59ad4731ae891ec2295af392be240d147f730d8a Mon Sep 17 00:00:00 2001
From: Jason Volk <jason@zemos.net>
Date: Tue, 10 Mar 2020 21:59:15 -0700
Subject: [PATCH] modules/m_relation: Add hook to discover and fetch events
 from m_relates_to.

---
 matrix/matrix.cc      |   1 +
 modules/Makefile.am   |   2 +
 modules/m_relation.cc | 147 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 150 insertions(+)
 create mode 100644 modules/m_relation.cc

diff --git a/matrix/matrix.cc b/matrix/matrix.cc
index 3f457879d..af54a28d3 100644
--- a/matrix/matrix.cc
+++ b/matrix/matrix.cc
@@ -71,6 +71,7 @@ ircd::m::matrix::module_names
 	"m_presence",
 	"m_profile",
 	"m_receipt",
+	"m_relation",
 	"m_room_aliases",
 	"m_room_canonical_alias",
 	"m_room_create",
diff --git a/modules/Makefile.am b/modules/Makefile.am
index d666f7b73..2fc63e993 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -101,6 +101,7 @@ m_noop_la_SOURCES = m_noop.cc
 m_presence_la_SOURCES = m_presence.cc
 m_profile_la_SOURCES = m_profile.cc
 m_receipt_la_SOURCES = m_receipt.cc
+m_relation_la_SOURCES = m_relation.cc
 m_room_aliases_la_SOURCES = m_room_aliases.cc
 m_room_canonical_alias_la_SOURCES = m_room_canonical_alias.cc
 m_room_create_la_SOURCES = m_room_create.cc
@@ -130,6 +131,7 @@ m_module_LTLIBRARIES = \
 	m_presence.la \
 	m_profile.la \
 	m_receipt.la \
+	m_relation.la \
 	m_room_aliases.la \
 	m_room_canonical_alias.la \
 	m_room_create.la \
diff --git a/modules/m_relation.cc b/modules/m_relation.cc
new file mode 100644
index 000000000..103942d8a
--- /dev/null
+++ b/modules/m_relation.cc
@@ -0,0 +1,147 @@
+// The Construct
+//
+// Copyright (C) The Construct Developers, Authors & Contributors
+// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice is present in all copies. The
+// full license for this software is available in the LICENSE file.
+
+namespace ircd::m::relation
+{
+	static void handle_fetch(const event &, vm::eval &);
+	extern hookfn<vm::eval &> fetch_hook;
+	extern conf::item<seconds> fetch_timeout;
+	extern conf::item<bool> fetch_enable;
+}
+
+ircd::mapi::header
+IRCD_MODULE
+{
+	"Matrix relations"
+};
+
+decltype(ircd::m::relation::fetch_enable)
+ircd::m::relation::fetch_enable
+{
+	{ "name",     "ircd.m.relation.fetch.enable" },
+	{ "default",  true                           },
+};
+
+decltype(ircd::m::relation::fetch_timeout)
+ircd::m::relation::fetch_timeout
+{
+	{ "name",     "ircd.m.relation.fetch.timeout" },
+	{ "default",  5L                              },
+};
+
+decltype(ircd::m::relation::fetch_hook)
+ircd::m::relation::fetch_hook
+{
+	handle_fetch,
+	{
+		{ "_site",  "vm.fetch" },
+	}
+};
+
+void
+ircd::m::relation::handle_fetch(const event &event,
+                                vm::eval &eval)
+try
+{
+	assert(eval.opts);
+	const auto &opts{*eval.opts};
+	if(!opts.fetch_prev)
+		return;
+
+	if(!fetch_enable)
+		return;
+
+	if(my(event))
+		return;
+
+	// event must be in a room for now; we won't have context until dht
+	if(!json::get<"room_id"_>(event))
+		return;
+
+	const auto &content
+	{
+		json::get<"content"_>(event)
+	};
+
+	const json::object &m_relates_to
+	{
+		content["m.relates_to"]
+	};
+
+	if(!m_relates_to || json::type(m_relates_to) != json::OBJECT)
+		return;
+
+	const event::id &event_id
+	{
+		m_relates_to["event_id"]
+	};
+
+	// If the relates_to is a prev_event then the vm::fetch unit will perform
+	// the fetch so this will just be redundant and we can bail.
+	const event::prev prev{event};
+	if(prev.prev_events_has(event_id))
+		return;
+
+	if(likely(m::exists(event_id)))
+		return;
+
+	log::dwarning
+	{
+		log, "%s in %s by %s relates to missing %s; fetching...",
+		string_view(event.event_id),
+		string_view(at<"room_id"_>(event)),
+		string_view(at<"sender"_>(event)),
+		string_view(event_id),
+	};
+
+	m::fetch::opts fetch_opts;
+	fetch_opts.op = m::fetch::op::event;
+	fetch_opts.room_id = at<"room_id"_>(event);
+	fetch_opts.event_id = event_id;
+	auto request
+	{
+		m::fetch::start(fetch_opts)
+	};
+
+	const auto response
+	{
+		request.get(seconds(fetch_timeout))
+	};
+
+	const m::event result
+	{
+		json::object
+		{
+			response
+		}
+	};
+
+	auto eval_opts(opts);
+	eval_opts.fetch_prev = false;
+	eval_opts.fetch_state = false;
+	vm::eval
+	{
+		result, eval_opts
+	};
+}
+catch(const ctx::interrupted &)
+{
+	throw;
+}
+catch(const std::exception &e)
+{
+	log::derror
+	{
+		log, "Failed to fetch relation for %s in %s :%s",
+		string_view(event.event_id),
+		string_view(json::get<"room_id"_>(event)),
+		e.what(),
+	};
+}