async-matrix 2.0.1-x86_64-darwin

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +200 -0
  3. data/README.md +133 -0
  4. data/data/discord-api-spec/openapi.json +40404 -0
  5. data/data/matrix-spec/api/client-server/account-data.yaml +389 -0
  6. data/data/matrix-spec/api/client-server/account_deactivation.yaml +149 -0
  7. data/data/matrix-spec/api/client-server/admin.yaml +512 -0
  8. data/data/matrix-spec/api/client-server/administrative_contact.yaml +620 -0
  9. data/data/matrix-spec/api/client-server/appservice_ping.yaml +183 -0
  10. data/data/matrix-spec/api/client-server/appservice_room_directory.yaml +104 -0
  11. data/data/matrix-spec/api/client-server/authed-content-repo.yaml +590 -0
  12. data/data/matrix-spec/api/client-server/banning.yaml +177 -0
  13. data/data/matrix-spec/api/client-server/capabilities.yaml +235 -0
  14. data/data/matrix-spec/api/client-server/content-repo.yaml +887 -0
  15. data/data/matrix-spec/api/client-server/create_room.yaml +306 -0
  16. data/data/matrix-spec/api/client-server/cross_signing.yaml +300 -0
  17. data/data/matrix-spec/api/client-server/definitions/auth_data.yaml +34 -0
  18. data/data/matrix-spec/api/client-server/definitions/auth_response.yaml +62 -0
  19. data/data/matrix-spec/api/client-server/definitions/client_device.yaml +44 -0
  20. data/data/matrix-spec/api/client-server/definitions/client_event.yaml +49 -0
  21. data/data/matrix-spec/api/client-server/definitions/client_event_without_room_id.yaml +147 -0
  22. data/data/matrix-spec/api/client-server/definitions/cross_signing_key.yaml +57 -0
  23. data/data/matrix-spec/api/client-server/definitions/device_keys.yaml +70 -0
  24. data/data/matrix-spec/api/client-server/definitions/errors/error.yaml +26 -0
  25. data/data/matrix-spec/api/client-server/definitions/errors/rate_limited.yaml +34 -0
  26. data/data/matrix-spec/api/client-server/definitions/event_batch.yaml +22 -0
  27. data/data/matrix-spec/api/client-server/definitions/event_filter.yaml +55 -0
  28. data/data/matrix-spec/api/client-server/definitions/invite_3pid.yaml +45 -0
  29. data/data/matrix-spec/api/client-server/definitions/key_backup_auth_data.yaml +36 -0
  30. data/data/matrix-spec/api/client-server/definitions/key_backup_data.yaml +50 -0
  31. data/data/matrix-spec/api/client-server/definitions/key_backup_session_data.yaml +57 -0
  32. data/data/matrix-spec/api/client-server/definitions/m.login.terms_params.yaml +83 -0
  33. data/data/matrix-spec/api/client-server/definitions/m.mentions.yaml +33 -0
  34. data/data/matrix-spec/api/client-server/definitions/m.oauth_params.yaml +30 -0
  35. data/data/matrix-spec/api/client-server/definitions/m.relates_to.yaml +45 -0
  36. data/data/matrix-spec/api/client-server/definitions/megolm_export_session_data.yaml +39 -0
  37. data/data/matrix-spec/api/client-server/definitions/olm_payload.yaml +88 -0
  38. data/data/matrix-spec/api/client-server/definitions/one_time_keys.yaml +27 -0
  39. data/data/matrix-spec/api/client-server/definitions/openid_token.yaml +37 -0
  40. data/data/matrix-spec/api/client-server/definitions/protocol.yaml +44 -0
  41. data/data/matrix-spec/api/client-server/definitions/public_rooms_chunk.yaml +74 -0
  42. data/data/matrix-spec/api/client-server/definitions/public_rooms_response.yaml +59 -0
  43. data/data/matrix-spec/api/client-server/definitions/push_condition.yaml +53 -0
  44. data/data/matrix-spec/api/client-server/definitions/push_rule.yaml +53 -0
  45. data/data/matrix-spec/api/client-server/definitions/push_ruleset.yaml +206 -0
  46. data/data/matrix-spec/api/client-server/definitions/recent_emoji.yaml +29 -0
  47. data/data/matrix-spec/api/client-server/definitions/request_email_validation.yaml +36 -0
  48. data/data/matrix-spec/api/client-server/definitions/request_msisdn_validation.yaml +36 -0
  49. data/data/matrix-spec/api/client-server/definitions/request_token_response.yaml +46 -0
  50. data/data/matrix-spec/api/client-server/definitions/room_event_filter.yaml +60 -0
  51. data/data/matrix-spec/api/client-server/definitions/room_key_backup.yaml +37 -0
  52. data/data/matrix-spec/api/client-server/definitions/room_summary.yaml +44 -0
  53. data/data/matrix-spec/api/client-server/definitions/security.yaml +47 -0
  54. data/data/matrix-spec/api/client-server/definitions/sso_login_flow.yaml +98 -0
  55. data/data/matrix-spec/api/client-server/definitions/state_event_batch.yaml +21 -0
  56. data/data/matrix-spec/api/client-server/definitions/sync_filter.yaml +86 -0
  57. data/data/matrix-spec/api/client-server/definitions/tag.yaml +24 -0
  58. data/data/matrix-spec/api/client-server/definitions/third_party_signed.yaml +45 -0
  59. data/data/matrix-spec/api/client-server/definitions/timeline_batch.yaml +36 -0
  60. data/data/matrix-spec/api/client-server/definitions/user_identifier.yaml +27 -0
  61. data/data/matrix-spec/api/client-server/definitions/wellknown/full.yaml +38 -0
  62. data/data/matrix-spec/api/client-server/definitions/wellknown/homeserver.yaml +25 -0
  63. data/data/matrix-spec/api/client-server/definitions/wellknown/identity_server.yaml +25 -0
  64. data/data/matrix-spec/api/client-server/device_management.yaml +314 -0
  65. data/data/matrix-spec/api/client-server/directory.yaml +318 -0
  66. data/data/matrix-spec/api/client-server/event_context.yaml +161 -0
  67. data/data/matrix-spec/api/client-server/filter.yaml +224 -0
  68. data/data/matrix-spec/api/client-server/inviting.yaml +149 -0
  69. data/data/matrix-spec/api/client-server/joining.yaml +238 -0
  70. data/data/matrix-spec/api/client-server/key_backup.yaml +993 -0
  71. data/data/matrix-spec/api/client-server/keys.yaml +482 -0
  72. data/data/matrix-spec/api/client-server/kicking.yaml +110 -0
  73. data/data/matrix-spec/api/client-server/knocking.yaml +152 -0
  74. data/data/matrix-spec/api/client-server/leaving.yaml +163 -0
  75. data/data/matrix-spec/api/client-server/list_joined_rooms.yaml +68 -0
  76. data/data/matrix-spec/api/client-server/list_public_rooms.yaml +280 -0
  77. data/data/matrix-spec/api/client-server/login.yaml +321 -0
  78. data/data/matrix-spec/api/client-server/login_token.yaml +138 -0
  79. data/data/matrix-spec/api/client-server/logout.yaml +86 -0
  80. data/data/matrix-spec/api/client-server/message_pagination.yaml +194 -0
  81. data/data/matrix-spec/api/client-server/notifications.yaml +152 -0
  82. data/data/matrix-spec/api/client-server/oauth_server_metadata.yaml +238 -0
  83. data/data/matrix-spec/api/client-server/old_sync.yaml +379 -0
  84. data/data/matrix-spec/api/client-server/openid.yaml +98 -0
  85. data/data/matrix-spec/api/client-server/password_management.yaml +246 -0
  86. data/data/matrix-spec/api/client-server/peeking_events.yaml +122 -0
  87. data/data/matrix-spec/api/client-server/policy_server.yaml +82 -0
  88. data/data/matrix-spec/api/client-server/presence.yaml +169 -0
  89. data/data/matrix-spec/api/client-server/profile.yaml +366 -0
  90. data/data/matrix-spec/api/client-server/pusher.yaml +303 -0
  91. data/data/matrix-spec/api/client-server/pushrules.yaml +633 -0
  92. data/data/matrix-spec/api/client-server/read_markers.yaml +103 -0
  93. data/data/matrix-spec/api/client-server/receipts.yaml +139 -0
  94. data/data/matrix-spec/api/client-server/redaction.yaml +116 -0
  95. data/data/matrix-spec/api/client-server/refresh.yaml +119 -0
  96. data/data/matrix-spec/api/client-server/registration.yaml +488 -0
  97. data/data/matrix-spec/api/client-server/registration_tokens.yaml +93 -0
  98. data/data/matrix-spec/api/client-server/relations.yaml +382 -0
  99. data/data/matrix-spec/api/client-server/report_content.yaml +283 -0
  100. data/data/matrix-spec/api/client-server/room_event_by_timestamp.yaml +147 -0
  101. data/data/matrix-spec/api/client-server/room_initial_sync.yaml +188 -0
  102. data/data/matrix-spec/api/client-server/room_send.yaml +130 -0
  103. data/data/matrix-spec/api/client-server/room_state.yaml +159 -0
  104. data/data/matrix-spec/api/client-server/room_summary.yaml +138 -0
  105. data/data/matrix-spec/api/client-server/room_upgrades.yaml +130 -0
  106. data/data/matrix-spec/api/client-server/rooms.yaml +380 -0
  107. data/data/matrix-spec/api/client-server/search.yaml +385 -0
  108. data/data/matrix-spec/api/client-server/space_hierarchy.yaml +237 -0
  109. data/data/matrix-spec/api/client-server/sso_login_redirect.yaml +135 -0
  110. data/data/matrix-spec/api/client-server/support.yaml +142 -0
  111. data/data/matrix-spec/api/client-server/sync.yaml +692 -0
  112. data/data/matrix-spec/api/client-server/tags.yaml +183 -0
  113. data/data/matrix-spec/api/client-server/third_party_lookup.yaml +324 -0
  114. data/data/matrix-spec/api/client-server/third_party_membership.yaml +139 -0
  115. data/data/matrix-spec/api/client-server/threads_list.yaml +167 -0
  116. data/data/matrix-spec/api/client-server/to_device.yaml +104 -0
  117. data/data/matrix-spec/api/client-server/typing.yaml +103 -0
  118. data/data/matrix-spec/api/client-server/users.yaml +136 -0
  119. data/data/matrix-spec/api/client-server/versions.yaml +108 -0
  120. data/data/matrix-spec/api/client-server/voip.yaml +93 -0
  121. data/data/matrix-spec/api/client-server/wellknown.yaml +60 -0
  122. data/data/matrix-spec/api/client-server/whoami.yaml +121 -0
  123. data/data/matrix-spec/event-schemas/examples/core/event.json +6 -0
  124. data/data/matrix-spec/event-schemas/examples/core/room_edu.json +3 -0
  125. data/data/matrix-spec/event-schemas/examples/core/room_event.json +11 -0
  126. data/data/matrix-spec/event-schemas/examples/core/state_event.json +4 -0
  127. data/data/matrix-spec/event-schemas/examples/invite_room_state.json +18 -0
  128. data/data/matrix-spec/event-schemas/examples/knock_room_state.json +18 -0
  129. data/data/matrix-spec/event-schemas/examples/m.accepted_terms.yaml +10 -0
  130. data/data/matrix-spec/event-schemas/examples/m.call.answer.yaml +21 -0
  131. data/data/matrix-spec/event-schemas/examples/m.call.candidates.yaml +16 -0
  132. data/data/matrix-spec/event-schemas/examples/m.call.hangup.yaml +10 -0
  133. data/data/matrix-spec/event-schemas/examples/m.call.invite.yaml +22 -0
  134. data/data/matrix-spec/event-schemas/examples/m.call.negotiate.yaml +22 -0
  135. data/data/matrix-spec/event-schemas/examples/m.call.reject.yaml +9 -0
  136. data/data/matrix-spec/event-schemas/examples/m.call.sdp_stream_metadata_changed.yaml +16 -0
  137. data/data/matrix-spec/event-schemas/examples/m.call.select_answer.yaml +10 -0
  138. data/data/matrix-spec/event-schemas/examples/m.direct.yaml +10 -0
  139. data/data/matrix-spec/event-schemas/examples/m.dummy.yaml +4 -0
  140. data/data/matrix-spec/event-schemas/examples/m.forwarded_room_key.yaml +14 -0
  141. data/data/matrix-spec/event-schemas/examples/m.fully_read.yaml +7 -0
  142. data/data/matrix-spec/event-schemas/examples/m.identity_server.yaml +7 -0
  143. data/data/matrix-spec/event-schemas/examples/m.ignored_user_list.yaml +9 -0
  144. data/data/matrix-spec/event-schemas/examples/m.invite_permission_config.yaml +7 -0
  145. data/data/matrix-spec/event-schemas/examples/m.key.verification.accept.yaml +12 -0
  146. data/data/matrix-spec/event-schemas/examples/m.key.verification.cancel.yaml +8 -0
  147. data/data/matrix-spec/event-schemas/examples/m.key.verification.done.yaml +6 -0
  148. data/data/matrix-spec/event-schemas/examples/m.key.verification.key.yaml +7 -0
  149. data/data/matrix-spec/event-schemas/examples/m.key.verification.mac.yaml +10 -0
  150. data/data/matrix-spec/event-schemas/examples/m.key.verification.ready.yaml +10 -0
  151. data/data/matrix-spec/event-schemas/examples/m.key.verification.request.yaml +11 -0
  152. data/data/matrix-spec/event-schemas/examples/m.key.verification.start$m.sas.v1.yaml +12 -0
  153. data/data/matrix-spec/event-schemas/examples/m.key.verification.start.yaml +8 -0
  154. data/data/matrix-spec/event-schemas/examples/m.key_backup.yaml +7 -0
  155. data/data/matrix-spec/event-schemas/examples/m.marked_unread.yaml +7 -0
  156. data/data/matrix-spec/event-schemas/examples/m.policy.rule.room.yaml +10 -0
  157. data/data/matrix-spec/event-schemas/examples/m.policy.rule.server.yaml +10 -0
  158. data/data/matrix-spec/event-schemas/examples/m.policy.rule.user.yaml +10 -0
  159. data/data/matrix-spec/event-schemas/examples/m.presence.yaml +12 -0
  160. data/data/matrix-spec/event-schemas/examples/m.push_rules.yaml +177 -0
  161. data/data/matrix-spec/event-schemas/examples/m.reaction.yaml +11 -0
  162. data/data/matrix-spec/event-schemas/examples/m.receipt.yaml +18 -0
  163. data/data/matrix-spec/event-schemas/examples/m.recent_emoji.yaml +16 -0
  164. data/data/matrix-spec/event-schemas/examples/m.room.avatar.yaml +14 -0
  165. data/data/matrix-spec/event-schemas/examples/m.room.canonical_alias.yaml +12 -0
  166. data/data/matrix-spec/event-schemas/examples/m.room.create.yaml +13 -0
  167. data/data/matrix-spec/event-schemas/examples/m.room.encrypted$megolm.yaml +11 -0
  168. data/data/matrix-spec/event-schemas/examples/m.room.encrypted$olm.yaml +14 -0
  169. data/data/matrix-spec/event-schemas/examples/m.room.encryption.yaml +10 -0
  170. data/data/matrix-spec/event-schemas/examples/m.room.guest_access.yaml +8 -0
  171. data/data/matrix-spec/event-schemas/examples/m.room.history_visibility.yaml +8 -0
  172. data/data/matrix-spec/event-schemas/examples/m.room.join_rules$restricted.yaml +18 -0
  173. data/data/matrix-spec/event-schemas/examples/m.room.join_rules.yaml +8 -0
  174. data/data/matrix-spec/event-schemas/examples/m.room.member$invite_room_state.yaml +15 -0
  175. data/data/matrix-spec/event-schemas/examples/m.room.member$join_authorised_via_users_server.yaml +12 -0
  176. data/data/matrix-spec/event-schemas/examples/m.room.member$knock_room_state.yaml +15 -0
  177. data/data/matrix-spec/event-schemas/examples/m.room.member$third_party_invite.yaml +20 -0
  178. data/data/matrix-spec/event-schemas/examples/m.room.member.yaml +12 -0
  179. data/data/matrix-spec/event-schemas/examples/m.room.message$m.audio.yaml +14 -0
  180. data/data/matrix-spec/event-schemas/examples/m.room.message$m.emote.yaml +10 -0
  181. data/data/matrix-spec/event-schemas/examples/m.room.message$m.file.yaml +14 -0
  182. data/data/matrix-spec/event-schemas/examples/m.room.message$m.image.yaml +16 -0
  183. data/data/matrix-spec/event-schemas/examples/m.room.message$m.key.verification.request.yaml +19 -0
  184. data/data/matrix-spec/event-schemas/examples/m.room.message$m.location.yaml +18 -0
  185. data/data/matrix-spec/event-schemas/examples/m.room.message$m.notice.yaml +10 -0
  186. data/data/matrix-spec/event-schemas/examples/m.room.message$m.server_notice.yaml +11 -0
  187. data/data/matrix-spec/event-schemas/examples/m.room.message$m.text.yaml +10 -0
  188. data/data/matrix-spec/event-schemas/examples/m.room.message$m.video.yaml +23 -0
  189. data/data/matrix-spec/event-schemas/examples/m.room.name.yaml +8 -0
  190. data/data/matrix-spec/event-schemas/examples/m.room.pinned_events.yaml +8 -0
  191. data/data/matrix-spec/event-schemas/examples/m.room.policy.yaml +11 -0
  192. data/data/matrix-spec/event-schemas/examples/m.room.power_levels.yaml +24 -0
  193. data/data/matrix-spec/event-schemas/examples/m.room.redaction.yaml +8 -0
  194. data/data/matrix-spec/event-schemas/examples/m.room.server_acl.yaml +10 -0
  195. data/data/matrix-spec/event-schemas/examples/m.room.third_party_invite.yaml +14 -0
  196. data/data/matrix-spec/event-schemas/examples/m.room.tombstone.yaml +9 -0
  197. data/data/matrix-spec/event-schemas/examples/m.room.topic.yaml +16 -0
  198. data/data/matrix-spec/event-schemas/examples/m.room_key.withheld.yaml +12 -0
  199. data/data/matrix-spec/event-schemas/examples/m.room_key.yaml +10 -0
  200. data/data/matrix-spec/event-schemas/examples/m.room_key_request$cancel_request.yaml +8 -0
  201. data/data/matrix-spec/event-schemas/examples/m.room_key_request$request.yaml +14 -0
  202. data/data/matrix-spec/event-schemas/examples/m.secret.request.yaml +9 -0
  203. data/data/matrix-spec/event-schemas/examples/m.secret.send.yaml +7 -0
  204. data/data/matrix-spec/event-schemas/examples/m.space.child.yaml +10 -0
  205. data/data/matrix-spec/event-schemas/examples/m.space.parent.yaml +9 -0
  206. data/data/matrix-spec/event-schemas/examples/m.sticker.yaml +22 -0
  207. data/data/matrix-spec/event-schemas/examples/m.tag.yaml +9 -0
  208. data/data/matrix-spec/event-schemas/examples/m.typing.yaml +7 -0
  209. data/data/matrix-spec/event-schemas/moderation_policy_rule.yaml +32 -0
  210. data/data/matrix-spec/event-schemas/schema/components/m_text_content_block.yaml +28 -0
  211. data/data/matrix-spec/event-schemas/schema/components/sdp_stream_metadata.yaml +43 -0
  212. data/data/matrix-spec/event-schemas/schema/components/signed_third_party_invite.yaml +48 -0
  213. data/data/matrix-spec/event-schemas/schema/core-event-schema/call_event.yaml +27 -0
  214. data/data/matrix-spec/event-schemas/schema/core-event-schema/event.yaml +16 -0
  215. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/avatar_info.yaml +30 -0
  216. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml +52 -0
  217. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/thumbnail_info.yaml +22 -0
  218. data/data/matrix-spec/event-schemas/schema/core-event-schema/room_event.yaml +17 -0
  219. data/data/matrix-spec/event-schemas/schema/core-event-schema/state_event.yaml +8 -0
  220. data/data/matrix-spec/event-schemas/schema/core-event-schema/stripped_state.yaml +48 -0
  221. data/data/matrix-spec/event-schemas/schema/core-event-schema/sync_room_event.yaml +49 -0
  222. data/data/matrix-spec/event-schemas/schema/core-event-schema/sync_state_event.yaml +36 -0
  223. data/data/matrix-spec/event-schemas/schema/core-event-schema/unsigned_prop.yaml +23 -0
  224. data/data/matrix-spec/event-schemas/schema/m.accepted_terms.yaml +25 -0
  225. data/data/matrix-spec/event-schemas/schema/m.call.answer.yaml +44 -0
  226. data/data/matrix-spec/event-schemas/schema/m.call.candidates.yaml +52 -0
  227. data/data/matrix-spec/event-schemas/schema/m.call.hangup.yaml +57 -0
  228. data/data/matrix-spec/event-schemas/schema/m.call.invite.yaml +53 -0
  229. data/data/matrix-spec/event-schemas/schema/m.call.negotiate.yaml +78 -0
  230. data/data/matrix-spec/event-schemas/schema/m.call.reject.yaml +29 -0
  231. data/data/matrix-spec/event-schemas/schema/m.call.sdp_stream_metadata_changed.yaml +24 -0
  232. data/data/matrix-spec/event-schemas/schema/m.call.select_answer.yaml +29 -0
  233. data/data/matrix-spec/event-schemas/schema/m.direct.yaml +29 -0
  234. data/data/matrix-spec/event-schemas/schema/m.dummy.yaml +25 -0
  235. data/data/matrix-spec/event-schemas/schema/m.forwarded_room_key.yaml +70 -0
  236. data/data/matrix-spec/event-schemas/schema/m.fully_read.yaml +26 -0
  237. data/data/matrix-spec/event-schemas/schema/m.identity_server.yaml +28 -0
  238. data/data/matrix-spec/event-schemas/schema/m.ignored_user_list.yaml +30 -0
  239. data/data/matrix-spec/event-schemas/schema/m.invite_permission_config.yaml +21 -0
  240. data/data/matrix-spec/event-schemas/schema/m.key.verification.accept.yaml +63 -0
  241. data/data/matrix-spec/event-schemas/schema/m.key.verification.cancel.yaml +72 -0
  242. data/data/matrix-spec/event-schemas/schema/m.key.verification.done.yaml +24 -0
  243. data/data/matrix-spec/event-schemas/schema/m.key.verification.key.yaml +31 -0
  244. data/data/matrix-spec/event-schemas/schema/m.key.verification.m.relates_to.yaml +23 -0
  245. data/data/matrix-spec/event-schemas/schema/m.key.verification.mac.yaml +43 -0
  246. data/data/matrix-spec/event-schemas/schema/m.key.verification.ready.yaml +41 -0
  247. data/data/matrix-spec/event-schemas/schema/m.key.verification.request.yaml +47 -0
  248. data/data/matrix-spec/event-schemas/schema/m.key.verification.start$m.reciprocate.v1.yaml +45 -0
  249. data/data/matrix-spec/event-schemas/schema/m.key.verification.start$m.sas.v1.yaml +76 -0
  250. data/data/matrix-spec/event-schemas/schema/m.key.verification.start.yaml +46 -0
  251. data/data/matrix-spec/event-schemas/schema/m.key_backup.yaml +24 -0
  252. data/data/matrix-spec/event-schemas/schema/m.marked_unread.yaml +26 -0
  253. data/data/matrix-spec/event-schemas/schema/m.policy.rule.room.yaml +17 -0
  254. data/data/matrix-spec/event-schemas/schema/m.policy.rule.server.yaml +17 -0
  255. data/data/matrix-spec/event-schemas/schema/m.policy.rule.user.yaml +17 -0
  256. data/data/matrix-spec/event-schemas/schema/m.presence.yaml +50 -0
  257. data/data/matrix-spec/event-schemas/schema/m.push_rules.yaml +23 -0
  258. data/data/matrix-spec/event-schemas/schema/m.reaction.yaml +45 -0
  259. data/data/matrix-spec/event-schemas/schema/m.receipt.yaml +65 -0
  260. data/data/matrix-spec/event-schemas/schema/m.recent_emoji.yaml +29 -0
  261. data/data/matrix-spec/event-schemas/schema/m.room.avatar.yaml +29 -0
  262. data/data/matrix-spec/event-schemas/schema/m.room.canonical_alias.yaml +40 -0
  263. data/data/matrix-spec/event-schemas/schema/m.room.create.yaml +86 -0
  264. data/data/matrix-spec/event-schemas/schema/m.room.encrypted.yaml +91 -0
  265. data/data/matrix-spec/event-schemas/schema/m.room.encryption.yaml +38 -0
  266. data/data/matrix-spec/event-schemas/schema/m.room.guest_access.yaml +28 -0
  267. data/data/matrix-spec/event-schemas/schema/m.room.history_visibility.yaml +30 -0
  268. data/data/matrix-spec/event-schemas/schema/m.room.join_rules.yaml +78 -0
  269. data/data/matrix-spec/event-schemas/schema/m.room.member.yaml +174 -0
  270. data/data/matrix-spec/event-schemas/schema/m.room.message$m.audio.yaml +73 -0
  271. data/data/matrix-spec/event-schemas/schema/m.room.message$m.emote.yaml +36 -0
  272. data/data/matrix-spec/event-schemas/schema/m.room.message$m.file.yaml +85 -0
  273. data/data/matrix-spec/event-schemas/schema/m.room.message$m.image.yaml +63 -0
  274. data/data/matrix-spec/event-schemas/schema/m.room.message$m.key.verification.request.yaml +70 -0
  275. data/data/matrix-spec/event-schemas/schema/m.room.message$m.location.yaml +50 -0
  276. data/data/matrix-spec/event-schemas/schema/m.room.message$m.notice.yaml +36 -0
  277. data/data/matrix-spec/event-schemas/schema/m.room.message$m.server_notice.yaml +41 -0
  278. data/data/matrix-spec/event-schemas/schema/m.room.message$m.text.yaml +36 -0
  279. data/data/matrix-spec/event-schemas/schema/m.room.message$m.video.yaml +95 -0
  280. data/data/matrix-spec/event-schemas/schema/m.room.message.yaml +25 -0
  281. data/data/matrix-spec/event-schemas/schema/m.room.name.yaml +35 -0
  282. data/data/matrix-spec/event-schemas/schema/m.room.pinned_events.yaml +27 -0
  283. data/data/matrix-spec/event-schemas/schema/m.room.policy.yaml +41 -0
  284. data/data/matrix-spec/event-schemas/schema/m.room.power_levels.yaml +139 -0
  285. data/data/matrix-spec/event-schemas/schema/m.room.redaction.yaml +29 -0
  286. data/data/matrix-spec/event-schemas/schema/m.room.server_acl.yaml +88 -0
  287. data/data/matrix-spec/event-schemas/schema/m.room.third_party_invite.yaml +79 -0
  288. data/data/matrix-spec/event-schemas/schema/m.room.tombstone.yaml +29 -0
  289. data/data/matrix-spec/event-schemas/schema/m.room.topic.yaml +53 -0
  290. data/data/matrix-spec/event-schemas/schema/m.room_key.withheld.yaml +88 -0
  291. data/data/matrix-spec/event-schemas/schema/m.room_key.yaml +38 -0
  292. data/data/matrix-spec/event-schemas/schema/m.room_key_request.yaml +73 -0
  293. data/data/matrix-spec/event-schemas/schema/m.secret.request.yaml +42 -0
  294. data/data/matrix-spec/event-schemas/schema/m.secret.send.yaml +35 -0
  295. data/data/matrix-spec/event-schemas/schema/m.space.child.yaml +50 -0
  296. data/data/matrix-spec/event-schemas/schema/m.space.parent.yaml +36 -0
  297. data/data/matrix-spec/event-schemas/schema/m.sticker.yaml +36 -0
  298. data/data/matrix-spec/event-schemas/schema/m.tag.yaml +28 -0
  299. data/data/matrix-spec/event-schemas/schema/m.typing.yaml +29 -0
  300. data/lib/async/discord/api/path_tree.rb +127 -0
  301. data/lib/async/discord/api.rb +151 -0
  302. data/lib/async/discord/client.rb +283 -0
  303. data/lib/async/discord/error.rb +84 -0
  304. data/lib/async/discord/gateway.rb +359 -0
  305. data/lib/async/discord.rb +15 -0
  306. data/lib/async/matrix/3.3/async_matrix_e2ee.bundle +0 -0
  307. data/lib/async/matrix/3.4/async_matrix_e2ee.bundle +0 -0
  308. data/lib/async/matrix/4.0/async_matrix_e2ee.bundle +0 -0
  309. data/lib/async/matrix/api/chain.rb +578 -0
  310. data/lib/async/matrix/api/concat.rb +101 -0
  311. data/lib/async/matrix/api/path_tree.rb +205 -0
  312. data/lib/async/matrix/api.rb +200 -0
  313. data/lib/async/matrix/application_service/bot.rb +232 -0
  314. data/lib/async/matrix/application_service/config/schema/analytics.json +21 -0
  315. data/lib/async/matrix/application_service/config/schema/appservice.json +82 -0
  316. data/lib/async/matrix/application_service/config/schema/backfill.json +91 -0
  317. data/lib/async/matrix/application_service/config/schema/bridge.json +209 -0
  318. data/lib/async/matrix/application_service/config/schema/config.json +61 -0
  319. data/lib/async/matrix/application_service/config/schema/database.json +38 -0
  320. data/lib/async/matrix/application_service/config/schema/direct_media.json +35 -0
  321. data/lib/async/matrix/application_service/config/schema/double_puppet.json +24 -0
  322. data/lib/async/matrix/application_service/config/schema/encryption.json +164 -0
  323. data/lib/async/matrix/application_service/config/schema/homeserver.json +58 -0
  324. data/lib/async/matrix/application_service/config/schema/logging.json +50 -0
  325. data/lib/async/matrix/application_service/config/schema/management_room_texts.json +25 -0
  326. data/lib/async/matrix/application_service/config/schema/matrix.json +45 -0
  327. data/lib/async/matrix/application_service/config/schema/permissions.json +54 -0
  328. data/lib/async/matrix/application_service/config/schema/provisioning.json +23 -0
  329. data/lib/async/matrix/application_service/config/schema/public_media.json +39 -0
  330. data/lib/async/matrix/application_service/config/schema/relay.json +43 -0
  331. data/lib/async/matrix/application_service/config/vivify.rb +109 -0
  332. data/lib/async/matrix/application_service/config.rb +225 -0
  333. data/lib/async/matrix/application_service/dispatcher.rb +185 -0
  334. data/lib/async/matrix/application_service/error_response.rb +37 -0
  335. data/lib/async/matrix/application_service/event.rb +285 -0
  336. data/lib/async/matrix/application_service/server.rb +430 -0
  337. data/lib/async/matrix/application_service/transaction.rb +66 -0
  338. data/lib/async/matrix/application_service/transaction_handler.rb +185 -0
  339. data/lib/async/matrix/application_service/transaction_store.rb +80 -0
  340. data/lib/async/matrix/bridge/discord/db/connection.rb +141 -0
  341. data/lib/async/matrix/bridge/discord/db/file.rb +118 -0
  342. data/lib/async/matrix/bridge/discord/db/guild.rb +120 -0
  343. data/lib/async/matrix/bridge/discord/db/message.rb +160 -0
  344. data/lib/async/matrix/bridge/discord/db/migrations/001_create_users.rb +14 -0
  345. data/lib/async/matrix/bridge/discord/db/migrations/002_create_guilds.rb +14 -0
  346. data/lib/async/matrix/bridge/discord/db/migrations/003_create_portals.rb +23 -0
  347. data/lib/async/matrix/bridge/discord/db/migrations/004_create_puppets.rb +19 -0
  348. data/lib/async/matrix/bridge/discord/db/migrations/005_create_messages.rb +20 -0
  349. data/lib/async/matrix/bridge/discord/db/migrations/006_create_reactions.rb +19 -0
  350. data/lib/async/matrix/bridge/discord/db/migrations/007_create_files.rb +18 -0
  351. data/lib/async/matrix/bridge/discord/db/portal.rb +150 -0
  352. data/lib/async/matrix/bridge/discord/db/puppet.rb +128 -0
  353. data/lib/async/matrix/bridge/discord/db/reaction.rb +165 -0
  354. data/lib/async/matrix/bridge/discord/db/schema.rb +18 -0
  355. data/lib/async/matrix/bridge/discord/db/user.rb +112 -0
  356. data/lib/async/matrix/bridge/discord/db.rb +138 -0
  357. data/lib/async/matrix/client.rb +934 -0
  358. data/lib/async/matrix/connection.rb +19 -0
  359. data/lib/async/matrix/double_puppet_client.rb +82 -0
  360. data/lib/async/matrix/e2ee.rb +92 -0
  361. data/lib/async/matrix/endpoint.rb +57 -0
  362. data/lib/async/matrix/error.rb +67 -0
  363. data/lib/async/matrix/media_client.rb +170 -0
  364. data/lib/async/matrix/notifier.rb +104 -0
  365. data/lib/async/matrix/schema/registry.rb +354 -0
  366. data/lib/async/matrix/schema/validation_error.rb +225 -0
  367. data/lib/async/matrix/schema.rb +170 -0
  368. data/lib/async/matrix/server.rb +18 -0
  369. data/lib/async/matrix/stream.rb +18 -0
  370. data/lib/async/matrix/version.rb +10 -0
  371. data/lib/async/matrix.rb +22 -0
  372. metadata +588 -0
@@ -0,0 +1,430 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the Apache License, Version 2.0.
4
+ # Copyright, 2026, by General Intelligence Systems.
5
+
6
+ require "forwardable"
7
+ require "json"
8
+ require "grape"
9
+ require "console"
10
+
11
+ module Async
12
+ module Matrix
13
+ module ApplicationService
14
+ # Matrix Application Service (server side).
15
+ #
16
+ # A +Server+ wraps a Grape::API into which the Matrix wire-protocol routes
17
+ # are mixed (via the {Server::Grape} concern). It forwards the Grape route
18
+ # DSL, so you can declare application-specific endpoints right alongside
19
+ # the Matrix routes, and register event handlers with #dispatch:
20
+ #
21
+ # server = Async::Matrix::ApplicationService::Server.new(
22
+ # hs_token: config.appservice.hs_token,
23
+ # client: Async::Matrix::Client.new(config)
24
+ # ) do
25
+ # dispatch do
26
+ # on "m.room.member" do |event|
27
+ # join_room(event.room_id) if event.content.membership == "invite"
28
+ # end
29
+ # end
30
+ #
31
+ # dispatch SomeHandler.new # plain handler or Bot
32
+ #
33
+ # post "/_webhook/send" do # your own endpoint, no Matrix auth
34
+ # client.send_text(params[:room_id], params[:body])
35
+ # {ok: true}
36
+ # end
37
+ # end
38
+ #
39
+ # run server
40
+ #
41
+ # +Server+ is a Rack app (it delegates #call to the wrapped Grape::API), so
42
+ # `run server` works directly, and it can be mounted into a larger Rack app.
43
+ #
44
+ # {Server::Grape} is a plain mix-in, so you can also skip +Server+ entirely
45
+ # and mix the routes straight into your own Grape::API:
46
+ #
47
+ # class MyAppService < Grape::API
48
+ # include Async::Matrix::ApplicationService::Server::Grape
49
+ # post("/_webhook/send") { ... }
50
+ # end
51
+ # MyAppService.configure { |c| c[:hs_token] = ...; c[:dispatcher] = ... }
52
+ class Server
53
+ extend Forwardable
54
+
55
+ # The Matrix Application Service wire protocol, as an includable concern.
56
+ # Mixing it into a Grape::API defines these routes (all under
57
+ # /_matrix/app/v1), reading its collaborators from Grape +configuration+:
58
+ # configuration[:hs_token] — homeserver token (authentication)
59
+ # configuration[:dispatcher] — Dispatcher receiving transactions
60
+ # configuration[:thirdparty] — optional third-party lookup handler
61
+ # configuration[:client] — optional Client (exposed to endpoints)
62
+ #
63
+ # Routes:
64
+ # PUT transactions/{txnId} — receive events (authenticated)
65
+ # POST ping — healthcheck (unauthenticated)
66
+ # GET users/{userId} — user existence query (authenticated)
67
+ # GET rooms/{roomAlias} — room alias query (authenticated)
68
+ # GET thirdparty/protocol/{proto} — protocol metadata (authenticated)
69
+ # GET thirdparty/location — location lookup (authenticated)
70
+ # GET thirdparty/location/{proto} — reverse location lookup (authenticated)
71
+ # GET thirdparty/user — user lookup (authenticated)
72
+ # GET thirdparty/user/{proto} — reverse user lookup (authenticated)
73
+ #
74
+ # Third-party queries delegate to configuration[:thirdparty], a duck type:
75
+ # protocol(name) -> Hash | nil (nil ⇒ 404 M_NOT_FOUND)
76
+ # locations(proto, params) -> Array (default [])
77
+ # users(proto, params) -> Array (default [])
78
+ module Grape
79
+ def self.included(base)
80
+ base.class_eval do
81
+ format :json
82
+ content_type :json, "application/json"
83
+ default_format :json
84
+
85
+ helpers do
86
+ def hs_token
87
+ configuration[:hs_token]
88
+ end
89
+
90
+ def dispatcher
91
+ configuration[:dispatcher]
92
+ end
93
+
94
+ def thirdparty
95
+ configuration[:thirdparty]
96
+ end
97
+
98
+ def client
99
+ configuration[:client]
100
+ end
101
+
102
+ # Homeserver token from the Authorization header (Bearer scheme)
103
+ # or the legacy access_token query parameter.
104
+ def request_token
105
+ auth = headers["Authorization"]
106
+ if auth&.start_with?("Bearer ")
107
+ auth.delete_prefix("Bearer ")
108
+ else
109
+ params[:access_token]
110
+ end
111
+ end
112
+
113
+ def authenticate!
114
+ token = request_token
115
+ unless token && secure_compare(token, hs_token.to_s)
116
+ error!({errcode: "M_FORBIDDEN"}, 403)
117
+ end
118
+ end
119
+
120
+ def secure_compare(a, b)
121
+ return false unless a.bytesize == b.bytesize
122
+
123
+ l = a.unpack("C*")
124
+ r = b.unpack("C*")
125
+ result = 0
126
+ l.each_with_index { |byte, i| result |= byte ^ r[i] }
127
+ result.zero?
128
+ end
129
+
130
+ # Raw JSON body, parsed here (rather than via Grape's param
131
+ # coercion) so malformed input yields the Matrix M_BAD_JSON.
132
+ def json_body
133
+ raw = request.body.read
134
+ request.body.rewind if request.body.respond_to?(:rewind)
135
+ return {} if raw.nil? || raw.empty?
136
+
137
+ JSON.parse(raw)
138
+ rescue JSON::ParserError => e
139
+ Console.error(self) { "Bad JSON in request: #{e.message}" }
140
+ error!({errcode: "M_BAD_JSON"}, 400)
141
+ end
142
+ end
143
+
144
+ # --- Healthcheck (unauthenticated) -----------------------------
145
+
146
+ post "/_matrix/app/v1/ping" do
147
+ status 200
148
+ {}
149
+ end
150
+
151
+ # --- Authenticated homeserver-facing routes --------------------
152
+
153
+ namespace "/_matrix/app/v1" do
154
+ before { authenticate! }
155
+
156
+ put "transactions/:txn_id" do
157
+ body = json_body
158
+ Console.info(self) do
159
+ events = body["events"] || []
160
+ "Transaction #{params[:txn_id]}: #{events.size} event(s)"
161
+ end
162
+ dispatcher.receive_transaction(params[:txn_id], body)
163
+ {}
164
+ end
165
+
166
+ get "users/:user_id", requirements: {user_id: %r{[^/]+}} do
167
+ {}
168
+ end
169
+
170
+ get "rooms/:room_alias", requirements: {room_alias: %r{[^/]+}} do
171
+ error!({errcode: "M_NOT_FOUND"}, 404)
172
+ end
173
+
174
+ # --- Third-party protocol lookups ---
175
+
176
+ get "thirdparty/protocol/:protocol" do
177
+ result = thirdparty&.protocol(params[:protocol])
178
+ error!({errcode: "M_NOT_FOUND"}, 404) unless result
179
+
180
+ result
181
+ end
182
+
183
+ get "thirdparty/location" do
184
+ thirdparty&.locations(nil, params) || []
185
+ end
186
+
187
+ get "thirdparty/location/:protocol" do
188
+ thirdparty&.locations(params[:protocol], params) || []
189
+ end
190
+
191
+ get "thirdparty/user" do
192
+ thirdparty&.users(nil, params) || []
193
+ end
194
+
195
+ get "thirdparty/user/:protocol" do
196
+ thirdparty&.users(params[:protocol], params) || []
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # Grape route DSL — forwarded to the wrapped API, so app-specific
204
+ # endpoints declared in the Server.new block land on the same Grape::API
205
+ # as the Matrix routes. `mount` is included, so you can compose in other
206
+ # Grape APIs too.
207
+ def_delegators :@api,
208
+ :get, :post, :put, :patch, :delete, :head, :options,
209
+ :namespace, :group, :resource, :resources, :route, :route_param,
210
+ :before, :after, :rescue_from, :helpers, :params, :desc, :use, :mount,
211
+ :version, :prefix, :format, :content_type,
212
+ # Rack + introspection:
213
+ :call, :routes, :recognize_path
214
+
215
+ attr_reader :api, :dispatcher, :client
216
+
217
+ def initialize(hs_token:, dispatcher: Dispatcher.new, client: nil, thirdparty: nil, &block)
218
+ @dispatcher = dispatcher
219
+ @client = client
220
+
221
+ # A fresh Grape::API with the Matrix routes mixed in, configured with
222
+ # our collaborators. No mounting — the routes are defined inline by the
223
+ # concern, so adding app-specific routes never triggers a re-mount.
224
+ @api = ::Class.new(::Grape::API)
225
+ @api.include(Grape)
226
+ @api.configure do |c|
227
+ c[:hs_token] = hs_token
228
+ c[:dispatcher] = dispatcher
229
+ c[:thirdparty] = thirdparty
230
+ c[:client] = client
231
+ end
232
+
233
+ instance_eval(&block) if block
234
+ end
235
+
236
+ # Register handlers with the dispatcher. Sugar over dispatcher.register:
237
+ #
238
+ # dispatch do # block → Bot built with `client`
239
+ # on "m.room.message" do |e| ... end
240
+ # end
241
+ # dispatch Bot.new(other_client) { } # explicit bot
242
+ # dispatch SomeHandler.new # plain handler
243
+ #
244
+ def dispatch(handler = nil, &block)
245
+ if block
246
+ unless @client
247
+ raise ArgumentError, "dispatch { ... } needs a client; pass `client:` to Server.new"
248
+ end
249
+
250
+ @dispatcher.register(Bot.new(@client, &block))
251
+ elsif handler
252
+ @dispatcher.register(handler)
253
+ else
254
+ raise ArgumentError, "dispatch requires a handler argument or a block"
255
+ end
256
+ @dispatcher
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ __END__
264
+ require "rack/mock"
265
+
266
+ describe "Async::Matrix::ApplicationService::Server" do
267
+ Server = Async::Matrix::ApplicationService::Server
268
+ Dispatcher = Async::Matrix::ApplicationService::Dispatcher
269
+
270
+ # Positional opts hash (not kwargs) so the helper survives scampi's
271
+ # nested-describe delegation, which forwards via *args.
272
+ def mock_req(app, method, path, opts = {})
273
+ env = {}
274
+ env["HTTP_AUTHORIZATION"] = "Bearer #{opts[:token]}" if opts[:token]
275
+ if opts[:body]
276
+ env["CONTENT_TYPE"] = "application/json"
277
+ env[:input] = opts[:body]
278
+ end
279
+ Rack::MockRequest.new(app).request(method, path, env)
280
+ end
281
+
282
+ def build(hs_token: "secret", dispatcher: nil, client: nil, thirdparty: nil, &block)
283
+ Server.new(hs_token: hs_token, dispatcher: dispatcher || Dispatcher.new,
284
+ client: client, thirdparty: thirdparty, &block)
285
+ end
286
+
287
+ # --- Matrix wire protocol ---
288
+
289
+ it "responds to POST /ping with 200 (no auth required)" do
290
+ mock_req(build, "POST", "/_matrix/app/v1/ping").status.should == 200
291
+ end
292
+
293
+ it "rejects GET /ping with 405" do
294
+ mock_req(build, "GET", "/_matrix/app/v1/ping").status.should == 405
295
+ end
296
+
297
+ it "rejects transactions without a token" do
298
+ mock_req(build, "PUT", "/_matrix/app/v1/transactions/txn1", body: "{}").status.should == 403
299
+ end
300
+
301
+ it "rejects transactions with wrong token" do
302
+ app = build(hs_token: "correct")
303
+ mock_req(app, "PUT", "/_matrix/app/v1/transactions/txn1", token: "wrong", body: "{}").status.should == 403
304
+ end
305
+
306
+ it "accepts valid transactions with Bearer auth" do
307
+ mock_req(build, "PUT", "/_matrix/app/v1/transactions/txn1",
308
+ token: "secret", body: '{"events":[]}').status.should == 200
309
+ end
310
+
311
+ it "dispatches events from a transaction" do
312
+ received = []
313
+ handler = Object.new
314
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
315
+ handler.define_singleton_method(:call) { |e| received << e }
316
+
317
+ app = build { dispatch handler }
318
+ mock_req(app, "PUT", "/_matrix/app/v1/transactions/txn_disp", token: "secret",
319
+ body: '{"events":[{"type":"m.room.message","content":{"body":"hi"}}]}')
320
+ received.length.should == 1
321
+ end
322
+
323
+ it "deduplicates transactions" do
324
+ received = []
325
+ handler = Object.new
326
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
327
+ handler.define_singleton_method(:call) { |e| received << e }
328
+
329
+ app = build { dispatch handler }
330
+ body = '{"events":[{"type":"m.room.message","content":{"body":"hi"}}]}'
331
+ mock_req(app, "PUT", "/_matrix/app/v1/transactions/dup", token: "secret", body: body)
332
+ resp = mock_req(app, "PUT", "/_matrix/app/v1/transactions/dup", token: "secret", body: body)
333
+
334
+ resp.status.should == 200
335
+ received.length.should == 1
336
+ end
337
+
338
+ it "rejects bad JSON with 400" do
339
+ mock_req(build, "PUT", "/_matrix/app/v1/transactions/txn1",
340
+ token: "secret", body: "not json").status.should == 400
341
+ end
342
+
343
+ it "responds 200 for user queries" do
344
+ mock_req(build, "GET", "/_matrix/app/v1/users/@bot:localhost", token: "secret").status.should == 200
345
+ end
346
+
347
+ it "responds 404 for room queries" do
348
+ mock_req(build, "GET", "/_matrix/app/v1/rooms/%23room:localhost", token: "secret").status.should == 404
349
+ end
350
+
351
+ it "returns [] for third-party user lookup with no handler" do
352
+ resp = mock_req(build, "GET", "/_matrix/app/v1/thirdparty/user", token: "secret")
353
+ resp.status.should == 200
354
+ resp.body.should == "[]"
355
+ end
356
+
357
+ it "404s third-party protocol lookup with no handler" do
358
+ mock_req(build, "GET", "/_matrix/app/v1/thirdparty/protocol/irc", token: "secret").status.should == 404
359
+ end
360
+
361
+ it "delegates third-party protocol lookup to the handler" do
362
+ tp = Object.new
363
+ tp.define_singleton_method(:protocol) { |name| {"instances" => [], "name" => name} }
364
+ app = build(thirdparty: tp)
365
+ resp = mock_req(app, "GET", "/_matrix/app/v1/thirdparty/protocol/irc", token: "secret")
366
+ resp.status.should == 200
367
+ JSON.parse(resp.body)["name"].should == "irc"
368
+ end
369
+
370
+ it "responds 404 for unknown paths" do
371
+ mock_req(build, "GET", "/unknown").status.should == 404
372
+ end
373
+
374
+ # --- App-specific endpoints via the forwarded DSL ---
375
+
376
+ it "serves app-specific routes declared with the forwarded post DSL" do
377
+ app = build do
378
+ post "/_webhook/send" do
379
+ {ok: true}
380
+ end
381
+ end
382
+ resp = mock_req(app, "POST", "/_webhook/send")
383
+ resp.status.should == 201
384
+ JSON.parse(resp.body)["ok"].should == true
385
+ end
386
+
387
+ it "does not require Matrix auth for app-specific routes" do
388
+ app = build { post("/_webhook/send") { {ok: true} } }
389
+ mock_req(app, "POST", "/_webhook/send").status.should == 201
390
+ end
391
+
392
+ it "does not duplicate the Matrix routes when app routes are added" do
393
+ app = build { post("/_webhook/send") { {} } }
394
+ ping_routes = app.routes.select { |r| r.path.include?("/ping") }
395
+ ping_routes.length.should == 1
396
+ end
397
+
398
+ # --- dispatch DSL ---
399
+
400
+ it "dispatch { on ... } builds a Bot from the block using the client" do
401
+ config = Async::Matrix::ApplicationService::Config.new({
402
+ "homeserver" => {"address" => "http://localhost:8008", "domain" => "localhost"},
403
+ "appservice" => {"as_token" => "a", "hs_token" => "secret", "bot" => {"username" => "bot"}}
404
+ })
405
+ bot_client = Async::Matrix::Client.new(config)
406
+
407
+ app = build(client: bot_client) do
408
+ dispatch do
409
+ on "m.room.message" do |event|
410
+ # not executed in this test
411
+ end
412
+ end
413
+ end
414
+ app.dispatcher.handler_count.should == 1
415
+ end
416
+
417
+ it "dispatch { } without a client raises ArgumentError" do
418
+ lambda { build { dispatch { on("m.room.message") { |e| } } } }.should.raise(ArgumentError)
419
+ end
420
+
421
+ it "isolates dispatcher and routes between instances" do
422
+ a = build(hs_token: "token_a") { post("/only_a") { {} } }
423
+ b = build(hs_token: "token_b")
424
+ mock_req(a, "PUT", "/_matrix/app/v1/transactions/x", token: "token_b", body: "{}").status.should == 403
425
+ mock_req(a, "PUT", "/_matrix/app/v1/transactions/x", token: "token_a", body: "{}").status.should == 200
426
+ # /only_a exists on `a` but not on `b`
427
+ mock_req(a, "POST", "/only_a").status.should == 201
428
+ mock_req(b, "POST", "/only_a").status.should == 404
429
+ end
430
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the Apache License, Version 2.0.
4
+ # Copyright, 2026, by General Intelligence Systems.
5
+
6
+ module Async
7
+ module Matrix
8
+ module ApplicationService
9
+ class Transaction
10
+ attr_reader :events, :ephemeral
11
+
12
+ def initialize(data)
13
+ @events = (data["events"] || []).map { |e| Schema.parse(e) }
14
+ @ephemeral = (data["de.sorunome.msc2409.ephemeral"] || data["ephemeral"] || []).map { |e| Schema.parse(e) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ __END__
22
+ describe "Async::Matrix::ApplicationService::Transaction" do
23
+ it "wraps events array" do
24
+ txn = Async::Matrix::ApplicationService::Transaction.new({
25
+ "events" => [
26
+ {"type" => "m.room.message", "content" => {"body" => "one"}},
27
+ {"type" => "m.room.message", "content" => {"body" => "two"}}
28
+ ]
29
+ })
30
+ txn.events.length.should == 2
31
+ txn.events.first.content.body.should == "one"
32
+ txn.events.last.content.body.should == "two"
33
+ end
34
+
35
+ it "defaults to empty events when missing" do
36
+ txn = Async::Matrix::ApplicationService::Transaction.new({})
37
+ txn.events.should.be.empty
38
+ end
39
+
40
+ it "parses MSC2409 ephemeral events" do
41
+ txn = Async::Matrix::ApplicationService::Transaction.new({
42
+ "events" => [],
43
+ "de.sorunome.msc2409.ephemeral" => [
44
+ {"type" => "m.typing", "content" => {}}
45
+ ]
46
+ })
47
+ txn.ephemeral.length.should == 1
48
+ txn.ephemeral.first.type.should == "m.typing"
49
+ end
50
+
51
+ it "falls back to ephemeral key" do
52
+ txn = Async::Matrix::ApplicationService::Transaction.new({
53
+ "events" => [],
54
+ "ephemeral" => [
55
+ {"type" => "m.receipt", "content" => {}}
56
+ ]
57
+ })
58
+ txn.ephemeral.length.should == 1
59
+ txn.ephemeral.first.type.should == "m.receipt"
60
+ end
61
+
62
+ it "defaults to empty ephemeral when missing" do
63
+ txn = Async::Matrix::ApplicationService::Transaction.new({"events" => []})
64
+ txn.ephemeral.should.be.empty
65
+ end
66
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the Apache License, Version 2.0.
4
+ # Copyright, 2026, by General Intelligence Systems.
5
+
6
+ require "console"
7
+
8
+ module Async
9
+ module Matrix
10
+ module ApplicationService
11
+ # Routes incoming Matrix events to registered handler objects.
12
+ #
13
+ # Each handler declares the event types it handles via `#event_types`.
14
+ # When an event arrives, the transaction_handler finds all matching handlers
15
+ # and calls them. Errors in one handler do not prevent others from running.
16
+ class TransactionHandler
17
+ def initialize(txn_store: TransactionStore.new)
18
+ @handlers = Hash.new { |h, k| h[k] = [] }
19
+ @txn_store = txn_store
20
+ end
21
+
22
+ # Register a Bot (responds to #handlers) or a plain handler (responds
23
+ # to #event_types and #call). Bots are expanded into their handlers.
24
+ def register(handler)
25
+ if handler.respond_to?(:handlers)
26
+ handler.handlers.each { |h| register(h) }
27
+ else
28
+ handler.event_types.each do |type|
29
+ @handlers[type] << handler
30
+ Console.info(self) { "Registered #{handler.class.name} for #{type}" }
31
+ end
32
+ end
33
+ end
34
+
35
+ # Idempotently process a homeserver transaction. Duplicate transaction
36
+ # IDs (already seen) are skipped. Returns :processed or :duplicate.
37
+ #
38
+ # The idempotency store lives here rather than in the HTTP layer because
39
+ # the Grape server is stateless across requests — the transaction_handler is the
40
+ # stable, long-lived object.
41
+ def receive_transaction(txn_id, body)
42
+ if @txn_store.seen?(txn_id)
43
+ Console.debug(self) { "Duplicate transaction #{txn_id} — skipping" }
44
+ :duplicate
45
+ else
46
+ dispatch_transaction(body)
47
+ @txn_store.mark_seen(txn_id)
48
+ :processed
49
+ end
50
+ end
51
+
52
+ def dispatch_transaction(body)
53
+ Transaction.new(body).then do |txn|
54
+ txn.events.each { |event| dispatch(event) }
55
+ txn.ephemeral.each { |event| dispatch(event) }
56
+ end
57
+ end
58
+
59
+ def dispatch(event)
60
+ type = event.type
61
+ handlers = @handlers[type]
62
+
63
+ if handlers.empty?
64
+ Console.debug(self) { "No handler for event type: #{type}" }
65
+ else
66
+ handlers.each do |handler|
67
+ begin
68
+ handler.call(event)
69
+ rescue => e
70
+ Console.error(self) { "Handler #{handler.class.name} raised #{e.class}: #{e.message}" }
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def handler_count
77
+ @handlers.values.flatten.size
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ __END__
85
+ describe "Async::Matrix::ApplicationService::TransactionHandler" do
86
+ it "registers and dispatches to handlers" do
87
+ received = []
88
+ handler = Object.new
89
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
90
+ handler.define_singleton_method(:call) { |event| received << event }
91
+
92
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
93
+ transaction_handler.register(handler)
94
+ transaction_handler.handler_count.should == 1
95
+
96
+ event = Async::Matrix::ApplicationService::Event.new({
97
+ "type" => "m.room.message",
98
+ "content" => {"body" => "hi"}
99
+ })
100
+ transaction_handler.dispatch(event)
101
+ received.length.should == 1
102
+ received.first.content.body.should == "hi"
103
+ end
104
+
105
+ it "ignores events with no matching handler" do
106
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
107
+ event = Async::Matrix::ApplicationService::Event.new({"type" => "m.unknown"})
108
+ lambda { transaction_handler.dispatch(event) }.should.not.raise
109
+ end
110
+
111
+ it "continues dispatching when a handler raises" do
112
+ results = []
113
+ bad_handler = Object.new
114
+ bad_handler.define_singleton_method(:event_types) { ["m.room.message"] }
115
+ bad_handler.define_singleton_method(:call) { |_| raise "boom" }
116
+
117
+ good_handler = Object.new
118
+ good_handler.define_singleton_method(:event_types) { ["m.room.message"] }
119
+ good_handler.define_singleton_method(:call) { |e| results << e }
120
+
121
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
122
+ transaction_handler.register(bad_handler)
123
+ transaction_handler.register(good_handler)
124
+
125
+ event = Async::Matrix::ApplicationService::Event.new({
126
+ "type" => "m.room.message",
127
+ "content" => {"body" => "test"}
128
+ })
129
+ transaction_handler.dispatch(event)
130
+ results.length.should == 1
131
+ end
132
+
133
+ it "dispatches a full transaction" do
134
+ received = []
135
+ handler = Object.new
136
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
137
+ handler.define_singleton_method(:call) { |e| received << e }
138
+
139
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
140
+ transaction_handler.register(handler)
141
+
142
+ transaction_handler.dispatch_transaction({
143
+ "events" => [
144
+ {"type" => "m.room.message", "content" => {"body" => "one"}},
145
+ {"type" => "m.room.message", "content" => {"body" => "two"}}
146
+ ]
147
+ })
148
+ received.length.should == 2
149
+ end
150
+
151
+ it "registers a bot (object responding to #handlers)" do
152
+ received = []
153
+ handler = Object.new
154
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
155
+ handler.define_singleton_method(:call) { |e| received << e }
156
+
157
+ bot = Object.new
158
+ bot.define_singleton_method(:handlers) { [handler] }
159
+
160
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
161
+ transaction_handler.register(bot)
162
+ transaction_handler.handler_count.should == 1
163
+
164
+ transaction_handler.dispatch_transaction({
165
+ "events" => [{"type" => "m.room.message", "content" => {"body" => "hi"}}]
166
+ })
167
+ received.length.should == 1
168
+ end
169
+
170
+ it "processes a transaction idempotently via receive_transaction" do
171
+ received = []
172
+ handler = Object.new
173
+ handler.define_singleton_method(:event_types) { ["m.room.message"] }
174
+ handler.define_singleton_method(:call) { |e| received << e }
175
+
176
+ transaction_handler = Async::Matrix::ApplicationService::TransactionHandler.new
177
+ transaction_handler.register(handler)
178
+
179
+ body = {"events" => [{"type" => "m.room.message", "content" => {"body" => "hi"}}]}
180
+
181
+ transaction_handler.receive_transaction("txn1", body).should == :processed
182
+ transaction_handler.receive_transaction("txn1", body).should == :duplicate
183
+ received.length.should == 1
184
+ end
185
+ end