diff --git a/packages/overlays/matrix-synapse/patches/0013-RequestRatelimiter-expose-can_do_action.patch b/packages/overlays/matrix-synapse/patches/0013-RequestRatelimiter-expose-can_do_action.patch
new file mode 100644
index 0000000..2ad8e55
--- /dev/null
+++ b/packages/overlays/matrix-synapse/patches/0013-RequestRatelimiter-expose-can_do_action.patch
@@ -0,0 +1,95 @@
+From 4b62d4e914d8ff7e21bcfbbc6572f1f2a363e066 Mon Sep 17 00:00:00 2001
+From: Rory& <root@rory.gay>
+Date: Fri, 25 Jul 2025 08:26:15 +0200
+Subject: [PATCH 13/14] RequestRatelimiter: expose can_do_action
+
+---
+ synapse/api/ratelimiting.py | 75 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 75 insertions(+)
+
+diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py
+index 509ef6b2c1..5f22089a6b 100644
+--- a/synapse/api/ratelimiting.py
++++ b/synapse/api/ratelimiting.py
+@@ -435,3 +435,78 @@ class RequestRatelimiter:
+ update=update,
+ n_actions=n_actions,
+ )
++
++ async def can_do_action(
++ self,
++ requester: Optional[Requester],
++ burst_count: Optional[int] = None,
++ update: bool = True,
++ is_admin_redaction: bool = False,
++ n_actions: int = 1,
++ ) -> Tuple[bool, float]:
++ """Can the entity (e.g. user or IP address) perform the action?
++
++ Checks if the user has ratelimiting disabled in the database by looking
++ for null/zero values in the `ratelimit_override` table. (Non-zero
++ values aren't honoured, as they're specific to the event sending
++ ratelimiter, rather than all ratelimiters)
++
++ Args:
++ requester: The requester that is doing the action, if any. Used to check
++ if the user has ratelimits disabled in the database.
++ key: An arbitrary key used to classify an action. Defaults to the
++ requester's user ID.
++ rate_hz: The long term number of actions that can be performed in a second.
++ Overrides the value set during instantiation if set.
++ burst_count: How many actions that can be performed before being limited.
++ Overrides the value set during instantiation if set.
++ update: Whether to count this check as performing the action. If the action
++ cannot be performed, the user's action count is not incremented at all.
++ n_actions: The number of times the user wants to do this action. If the user
++ cannot do all of the actions, the user's action count is not incremented
++ at all.
++ _time_now_s: The current time. Optional, defaults to the current time according
++ to self.clock. Only used by tests.
++
++ Returns:
++ A tuple containing:
++ * A bool indicating if they can perform the action now
++ * The reactor timestamp for when the action can be performed next.
++ -1 if rate_hz is less than or equal to zero
++ """
++ user_id = requester.user.to_string()
++
++ # The AS user itself is never rate limited.
++ app_service = self.store.get_app_service_by_user_id(user_id)
++ if app_service is not None:
++ return True, 0 # do not ratelimit app service senders
++
++ messages_per_second = self._rc_message.per_second
++ burst_count = self._rc_message.burst_count
++
++ # Check if there is a per user override in the DB.
++ override = await self.store.get_ratelimit_for_user(user_id)
++ if override:
++ # If overridden with a null Hz then ratelimiting has been entirely
++ # disabled for the user
++ if not override.messages_per_second:
++ return True, 0
++
++ messages_per_second = override.messages_per_second
++ burst_count = override.burst_count
++
++ if is_admin_redaction and self.admin_redaction_ratelimiter:
++ # If we have separate config for admin redactions, use a separate
++ # ratelimiter as to not have user_ids clash
++ return await self.admin_redaction_ratelimiter.can_do_action(
++ requester, update=update, n_actions=n_actions
++ )
++ else:
++ # Override rate and burst count per-user
++ return await self.request_ratelimiter.can_do_action(
++ requester,
++ rate_hz=messages_per_second,
++ burst_count=burst_count,
++ update=update,
++ n_actions=n_actions,
++ )
+--
+2.49.0
+
|