async-matrix 0.1.2 → 1.0.0

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 (341) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +131 -0
  3. data/data/matrix-spec/api/client-server/account-data.yaml +389 -0
  4. data/data/matrix-spec/api/client-server/account_deactivation.yaml +149 -0
  5. data/data/matrix-spec/api/client-server/admin.yaml +512 -0
  6. data/data/matrix-spec/api/client-server/administrative_contact.yaml +620 -0
  7. data/data/matrix-spec/api/client-server/appservice_ping.yaml +183 -0
  8. data/data/matrix-spec/api/client-server/appservice_room_directory.yaml +104 -0
  9. data/data/matrix-spec/api/client-server/authed-content-repo.yaml +590 -0
  10. data/data/matrix-spec/api/client-server/banning.yaml +177 -0
  11. data/data/matrix-spec/api/client-server/capabilities.yaml +235 -0
  12. data/data/matrix-spec/api/client-server/content-repo.yaml +887 -0
  13. data/data/matrix-spec/api/client-server/create_room.yaml +306 -0
  14. data/data/matrix-spec/api/client-server/cross_signing.yaml +300 -0
  15. data/data/matrix-spec/api/client-server/definitions/auth_data.yaml +34 -0
  16. data/data/matrix-spec/api/client-server/definitions/auth_response.yaml +62 -0
  17. data/data/matrix-spec/api/client-server/definitions/client_device.yaml +44 -0
  18. data/data/matrix-spec/api/client-server/definitions/client_event.yaml +49 -0
  19. data/data/matrix-spec/api/client-server/definitions/client_event_without_room_id.yaml +147 -0
  20. data/data/matrix-spec/api/client-server/definitions/cross_signing_key.yaml +57 -0
  21. data/data/matrix-spec/api/client-server/definitions/device_keys.yaml +70 -0
  22. data/data/matrix-spec/api/client-server/definitions/errors/error.yaml +26 -0
  23. data/data/matrix-spec/api/client-server/definitions/errors/rate_limited.yaml +34 -0
  24. data/data/matrix-spec/api/client-server/definitions/event_batch.yaml +22 -0
  25. data/data/matrix-spec/api/client-server/definitions/event_filter.yaml +55 -0
  26. data/data/matrix-spec/api/client-server/definitions/invite_3pid.yaml +45 -0
  27. data/data/matrix-spec/api/client-server/definitions/key_backup_auth_data.yaml +36 -0
  28. data/data/matrix-spec/api/client-server/definitions/key_backup_data.yaml +50 -0
  29. data/data/matrix-spec/api/client-server/definitions/key_backup_session_data.yaml +57 -0
  30. data/data/matrix-spec/api/client-server/definitions/m.login.terms_params.yaml +83 -0
  31. data/data/matrix-spec/api/client-server/definitions/m.mentions.yaml +33 -0
  32. data/data/matrix-spec/api/client-server/definitions/m.oauth_params.yaml +30 -0
  33. data/data/matrix-spec/api/client-server/definitions/m.relates_to.yaml +45 -0
  34. data/data/matrix-spec/api/client-server/definitions/megolm_export_session_data.yaml +39 -0
  35. data/data/matrix-spec/api/client-server/definitions/olm_payload.yaml +88 -0
  36. data/data/matrix-spec/api/client-server/definitions/one_time_keys.yaml +27 -0
  37. data/data/matrix-spec/api/client-server/definitions/openid_token.yaml +37 -0
  38. data/data/matrix-spec/api/client-server/definitions/protocol.yaml +44 -0
  39. data/data/matrix-spec/api/client-server/definitions/public_rooms_chunk.yaml +74 -0
  40. data/data/matrix-spec/api/client-server/definitions/public_rooms_response.yaml +59 -0
  41. data/data/matrix-spec/api/client-server/definitions/push_condition.yaml +53 -0
  42. data/data/matrix-spec/api/client-server/definitions/push_rule.yaml +53 -0
  43. data/data/matrix-spec/api/client-server/definitions/push_ruleset.yaml +206 -0
  44. data/data/matrix-spec/api/client-server/definitions/recent_emoji.yaml +29 -0
  45. data/data/matrix-spec/api/client-server/definitions/request_email_validation.yaml +36 -0
  46. data/data/matrix-spec/api/client-server/definitions/request_msisdn_validation.yaml +36 -0
  47. data/data/matrix-spec/api/client-server/definitions/request_token_response.yaml +46 -0
  48. data/data/matrix-spec/api/client-server/definitions/room_event_filter.yaml +60 -0
  49. data/data/matrix-spec/api/client-server/definitions/room_key_backup.yaml +37 -0
  50. data/data/matrix-spec/api/client-server/definitions/room_summary.yaml +44 -0
  51. data/data/matrix-spec/api/client-server/definitions/security.yaml +47 -0
  52. data/data/matrix-spec/api/client-server/definitions/sso_login_flow.yaml +98 -0
  53. data/data/matrix-spec/api/client-server/definitions/state_event_batch.yaml +21 -0
  54. data/data/matrix-spec/api/client-server/definitions/sync_filter.yaml +86 -0
  55. data/data/matrix-spec/api/client-server/definitions/tag.yaml +24 -0
  56. data/data/matrix-spec/api/client-server/definitions/third_party_signed.yaml +45 -0
  57. data/data/matrix-spec/api/client-server/definitions/timeline_batch.yaml +36 -0
  58. data/data/matrix-spec/api/client-server/definitions/user_identifier.yaml +27 -0
  59. data/data/matrix-spec/api/client-server/definitions/wellknown/full.yaml +38 -0
  60. data/data/matrix-spec/api/client-server/definitions/wellknown/homeserver.yaml +25 -0
  61. data/data/matrix-spec/api/client-server/definitions/wellknown/identity_server.yaml +25 -0
  62. data/data/matrix-spec/api/client-server/device_management.yaml +314 -0
  63. data/data/matrix-spec/api/client-server/directory.yaml +318 -0
  64. data/data/matrix-spec/api/client-server/event_context.yaml +161 -0
  65. data/data/matrix-spec/api/client-server/filter.yaml +224 -0
  66. data/data/matrix-spec/api/client-server/inviting.yaml +149 -0
  67. data/data/matrix-spec/api/client-server/joining.yaml +238 -0
  68. data/data/matrix-spec/api/client-server/key_backup.yaml +993 -0
  69. data/data/matrix-spec/api/client-server/keys.yaml +482 -0
  70. data/data/matrix-spec/api/client-server/kicking.yaml +110 -0
  71. data/data/matrix-spec/api/client-server/knocking.yaml +152 -0
  72. data/data/matrix-spec/api/client-server/leaving.yaml +163 -0
  73. data/data/matrix-spec/api/client-server/list_joined_rooms.yaml +68 -0
  74. data/data/matrix-spec/api/client-server/list_public_rooms.yaml +280 -0
  75. data/data/matrix-spec/api/client-server/login.yaml +321 -0
  76. data/data/matrix-spec/api/client-server/login_token.yaml +138 -0
  77. data/data/matrix-spec/api/client-server/logout.yaml +86 -0
  78. data/data/matrix-spec/api/client-server/message_pagination.yaml +194 -0
  79. data/data/matrix-spec/api/client-server/notifications.yaml +152 -0
  80. data/data/matrix-spec/api/client-server/oauth_server_metadata.yaml +238 -0
  81. data/data/matrix-spec/api/client-server/old_sync.yaml +379 -0
  82. data/data/matrix-spec/api/client-server/openid.yaml +98 -0
  83. data/data/matrix-spec/api/client-server/password_management.yaml +246 -0
  84. data/data/matrix-spec/api/client-server/peeking_events.yaml +122 -0
  85. data/data/matrix-spec/api/client-server/policy_server.yaml +82 -0
  86. data/data/matrix-spec/api/client-server/presence.yaml +169 -0
  87. data/data/matrix-spec/api/client-server/profile.yaml +366 -0
  88. data/data/matrix-spec/api/client-server/pusher.yaml +303 -0
  89. data/data/matrix-spec/api/client-server/pushrules.yaml +633 -0
  90. data/data/matrix-spec/api/client-server/read_markers.yaml +103 -0
  91. data/data/matrix-spec/api/client-server/receipts.yaml +139 -0
  92. data/data/matrix-spec/api/client-server/redaction.yaml +116 -0
  93. data/data/matrix-spec/api/client-server/refresh.yaml +119 -0
  94. data/data/matrix-spec/api/client-server/registration.yaml +488 -0
  95. data/data/matrix-spec/api/client-server/registration_tokens.yaml +93 -0
  96. data/data/matrix-spec/api/client-server/relations.yaml +382 -0
  97. data/data/matrix-spec/api/client-server/report_content.yaml +283 -0
  98. data/data/matrix-spec/api/client-server/room_event_by_timestamp.yaml +147 -0
  99. data/data/matrix-spec/api/client-server/room_initial_sync.yaml +188 -0
  100. data/data/matrix-spec/api/client-server/room_send.yaml +130 -0
  101. data/data/matrix-spec/api/client-server/room_state.yaml +159 -0
  102. data/data/matrix-spec/api/client-server/room_summary.yaml +138 -0
  103. data/data/matrix-spec/api/client-server/room_upgrades.yaml +130 -0
  104. data/data/matrix-spec/api/client-server/rooms.yaml +380 -0
  105. data/data/matrix-spec/api/client-server/search.yaml +385 -0
  106. data/data/matrix-spec/api/client-server/space_hierarchy.yaml +237 -0
  107. data/data/matrix-spec/api/client-server/sso_login_redirect.yaml +135 -0
  108. data/data/matrix-spec/api/client-server/support.yaml +142 -0
  109. data/data/matrix-spec/api/client-server/sync.yaml +692 -0
  110. data/data/matrix-spec/api/client-server/tags.yaml +183 -0
  111. data/data/matrix-spec/api/client-server/third_party_lookup.yaml +324 -0
  112. data/data/matrix-spec/api/client-server/third_party_membership.yaml +139 -0
  113. data/data/matrix-spec/api/client-server/threads_list.yaml +167 -0
  114. data/data/matrix-spec/api/client-server/to_device.yaml +104 -0
  115. data/data/matrix-spec/api/client-server/typing.yaml +103 -0
  116. data/data/matrix-spec/api/client-server/users.yaml +136 -0
  117. data/data/matrix-spec/api/client-server/versions.yaml +108 -0
  118. data/data/matrix-spec/api/client-server/voip.yaml +93 -0
  119. data/data/matrix-spec/api/client-server/wellknown.yaml +60 -0
  120. data/data/matrix-spec/api/client-server/whoami.yaml +121 -0
  121. data/data/matrix-spec/event-schemas/examples/core/event.json +6 -0
  122. data/data/matrix-spec/event-schemas/examples/core/room_edu.json +3 -0
  123. data/data/matrix-spec/event-schemas/examples/core/room_event.json +11 -0
  124. data/data/matrix-spec/event-schemas/examples/core/state_event.json +4 -0
  125. data/data/matrix-spec/event-schemas/examples/invite_room_state.json +18 -0
  126. data/data/matrix-spec/event-schemas/examples/knock_room_state.json +18 -0
  127. data/data/matrix-spec/event-schemas/examples/m.accepted_terms.yaml +10 -0
  128. data/data/matrix-spec/event-schemas/examples/m.call.answer.yaml +21 -0
  129. data/data/matrix-spec/event-schemas/examples/m.call.candidates.yaml +16 -0
  130. data/data/matrix-spec/event-schemas/examples/m.call.hangup.yaml +10 -0
  131. data/data/matrix-spec/event-schemas/examples/m.call.invite.yaml +22 -0
  132. data/data/matrix-spec/event-schemas/examples/m.call.negotiate.yaml +22 -0
  133. data/data/matrix-spec/event-schemas/examples/m.call.reject.yaml +9 -0
  134. data/data/matrix-spec/event-schemas/examples/m.call.sdp_stream_metadata_changed.yaml +16 -0
  135. data/data/matrix-spec/event-schemas/examples/m.call.select_answer.yaml +10 -0
  136. data/data/matrix-spec/event-schemas/examples/m.direct.yaml +10 -0
  137. data/data/matrix-spec/event-schemas/examples/m.dummy.yaml +4 -0
  138. data/data/matrix-spec/event-schemas/examples/m.forwarded_room_key.yaml +14 -0
  139. data/data/matrix-spec/event-schemas/examples/m.fully_read.yaml +7 -0
  140. data/data/matrix-spec/event-schemas/examples/m.identity_server.yaml +7 -0
  141. data/data/matrix-spec/event-schemas/examples/m.ignored_user_list.yaml +9 -0
  142. data/data/matrix-spec/event-schemas/examples/m.invite_permission_config.yaml +7 -0
  143. data/data/matrix-spec/event-schemas/examples/m.key.verification.accept.yaml +12 -0
  144. data/data/matrix-spec/event-schemas/examples/m.key.verification.cancel.yaml +8 -0
  145. data/data/matrix-spec/event-schemas/examples/m.key.verification.done.yaml +6 -0
  146. data/data/matrix-spec/event-schemas/examples/m.key.verification.key.yaml +7 -0
  147. data/data/matrix-spec/event-schemas/examples/m.key.verification.mac.yaml +10 -0
  148. data/data/matrix-spec/event-schemas/examples/m.key.verification.ready.yaml +10 -0
  149. data/data/matrix-spec/event-schemas/examples/m.key.verification.request.yaml +11 -0
  150. data/data/matrix-spec/event-schemas/examples/m.key.verification.start$m.sas.v1.yaml +12 -0
  151. data/data/matrix-spec/event-schemas/examples/m.key.verification.start.yaml +8 -0
  152. data/data/matrix-spec/event-schemas/examples/m.key_backup.yaml +7 -0
  153. data/data/matrix-spec/event-schemas/examples/m.marked_unread.yaml +7 -0
  154. data/data/matrix-spec/event-schemas/examples/m.policy.rule.room.yaml +10 -0
  155. data/data/matrix-spec/event-schemas/examples/m.policy.rule.server.yaml +10 -0
  156. data/data/matrix-spec/event-schemas/examples/m.policy.rule.user.yaml +10 -0
  157. data/data/matrix-spec/event-schemas/examples/m.presence.yaml +12 -0
  158. data/data/matrix-spec/event-schemas/examples/m.push_rules.yaml +177 -0
  159. data/data/matrix-spec/event-schemas/examples/m.reaction.yaml +11 -0
  160. data/data/matrix-spec/event-schemas/examples/m.receipt.yaml +18 -0
  161. data/data/matrix-spec/event-schemas/examples/m.recent_emoji.yaml +16 -0
  162. data/data/matrix-spec/event-schemas/examples/m.room.avatar.yaml +14 -0
  163. data/data/matrix-spec/event-schemas/examples/m.room.canonical_alias.yaml +12 -0
  164. data/data/matrix-spec/event-schemas/examples/m.room.create.yaml +13 -0
  165. data/data/matrix-spec/event-schemas/examples/m.room.encrypted$megolm.yaml +11 -0
  166. data/data/matrix-spec/event-schemas/examples/m.room.encrypted$olm.yaml +14 -0
  167. data/data/matrix-spec/event-schemas/examples/m.room.encryption.yaml +10 -0
  168. data/data/matrix-spec/event-schemas/examples/m.room.guest_access.yaml +8 -0
  169. data/data/matrix-spec/event-schemas/examples/m.room.history_visibility.yaml +8 -0
  170. data/data/matrix-spec/event-schemas/examples/m.room.join_rules$restricted.yaml +18 -0
  171. data/data/matrix-spec/event-schemas/examples/m.room.join_rules.yaml +8 -0
  172. data/data/matrix-spec/event-schemas/examples/m.room.member$invite_room_state.yaml +15 -0
  173. data/data/matrix-spec/event-schemas/examples/m.room.member$join_authorised_via_users_server.yaml +12 -0
  174. data/data/matrix-spec/event-schemas/examples/m.room.member$knock_room_state.yaml +15 -0
  175. data/data/matrix-spec/event-schemas/examples/m.room.member$third_party_invite.yaml +20 -0
  176. data/data/matrix-spec/event-schemas/examples/m.room.member.yaml +12 -0
  177. data/data/matrix-spec/event-schemas/examples/m.room.message$m.audio.yaml +14 -0
  178. data/data/matrix-spec/event-schemas/examples/m.room.message$m.emote.yaml +10 -0
  179. data/data/matrix-spec/event-schemas/examples/m.room.message$m.file.yaml +14 -0
  180. data/data/matrix-spec/event-schemas/examples/m.room.message$m.image.yaml +16 -0
  181. data/data/matrix-spec/event-schemas/examples/m.room.message$m.key.verification.request.yaml +19 -0
  182. data/data/matrix-spec/event-schemas/examples/m.room.message$m.location.yaml +18 -0
  183. data/data/matrix-spec/event-schemas/examples/m.room.message$m.notice.yaml +10 -0
  184. data/data/matrix-spec/event-schemas/examples/m.room.message$m.server_notice.yaml +11 -0
  185. data/data/matrix-spec/event-schemas/examples/m.room.message$m.text.yaml +10 -0
  186. data/data/matrix-spec/event-schemas/examples/m.room.message$m.video.yaml +23 -0
  187. data/data/matrix-spec/event-schemas/examples/m.room.name.yaml +8 -0
  188. data/data/matrix-spec/event-schemas/examples/m.room.pinned_events.yaml +8 -0
  189. data/data/matrix-spec/event-schemas/examples/m.room.policy.yaml +11 -0
  190. data/data/matrix-spec/event-schemas/examples/m.room.power_levels.yaml +24 -0
  191. data/data/matrix-spec/event-schemas/examples/m.room.redaction.yaml +8 -0
  192. data/data/matrix-spec/event-schemas/examples/m.room.server_acl.yaml +10 -0
  193. data/data/matrix-spec/event-schemas/examples/m.room.third_party_invite.yaml +14 -0
  194. data/data/matrix-spec/event-schemas/examples/m.room.tombstone.yaml +9 -0
  195. data/data/matrix-spec/event-schemas/examples/m.room.topic.yaml +16 -0
  196. data/data/matrix-spec/event-schemas/examples/m.room_key.withheld.yaml +12 -0
  197. data/data/matrix-spec/event-schemas/examples/m.room_key.yaml +10 -0
  198. data/data/matrix-spec/event-schemas/examples/m.room_key_request$cancel_request.yaml +8 -0
  199. data/data/matrix-spec/event-schemas/examples/m.room_key_request$request.yaml +14 -0
  200. data/data/matrix-spec/event-schemas/examples/m.secret.request.yaml +9 -0
  201. data/data/matrix-spec/event-schemas/examples/m.secret.send.yaml +7 -0
  202. data/data/matrix-spec/event-schemas/examples/m.space.child.yaml +10 -0
  203. data/data/matrix-spec/event-schemas/examples/m.space.parent.yaml +9 -0
  204. data/data/matrix-spec/event-schemas/examples/m.sticker.yaml +22 -0
  205. data/data/matrix-spec/event-schemas/examples/m.tag.yaml +9 -0
  206. data/data/matrix-spec/event-schemas/examples/m.typing.yaml +7 -0
  207. data/data/matrix-spec/event-schemas/moderation_policy_rule.yaml +32 -0
  208. data/data/matrix-spec/event-schemas/schema/components/m_text_content_block.yaml +28 -0
  209. data/data/matrix-spec/event-schemas/schema/components/sdp_stream_metadata.yaml +43 -0
  210. data/data/matrix-spec/event-schemas/schema/components/signed_third_party_invite.yaml +48 -0
  211. data/data/matrix-spec/event-schemas/schema/core-event-schema/call_event.yaml +27 -0
  212. data/data/matrix-spec/event-schemas/schema/core-event-schema/event.yaml +16 -0
  213. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/avatar_info.yaml +30 -0
  214. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml +52 -0
  215. data/data/matrix-spec/event-schemas/schema/core-event-schema/msgtype_infos/thumbnail_info.yaml +22 -0
  216. data/data/matrix-spec/event-schemas/schema/core-event-schema/room_event.yaml +17 -0
  217. data/data/matrix-spec/event-schemas/schema/core-event-schema/state_event.yaml +8 -0
  218. data/data/matrix-spec/event-schemas/schema/core-event-schema/stripped_state.yaml +48 -0
  219. data/data/matrix-spec/event-schemas/schema/core-event-schema/sync_room_event.yaml +49 -0
  220. data/data/matrix-spec/event-schemas/schema/core-event-schema/sync_state_event.yaml +36 -0
  221. data/data/matrix-spec/event-schemas/schema/core-event-schema/unsigned_prop.yaml +23 -0
  222. data/data/matrix-spec/event-schemas/schema/m.accepted_terms.yaml +25 -0
  223. data/data/matrix-spec/event-schemas/schema/m.call.answer.yaml +44 -0
  224. data/data/matrix-spec/event-schemas/schema/m.call.candidates.yaml +52 -0
  225. data/data/matrix-spec/event-schemas/schema/m.call.hangup.yaml +57 -0
  226. data/data/matrix-spec/event-schemas/schema/m.call.invite.yaml +53 -0
  227. data/data/matrix-spec/event-schemas/schema/m.call.negotiate.yaml +78 -0
  228. data/data/matrix-spec/event-schemas/schema/m.call.reject.yaml +29 -0
  229. data/data/matrix-spec/event-schemas/schema/m.call.sdp_stream_metadata_changed.yaml +24 -0
  230. data/data/matrix-spec/event-schemas/schema/m.call.select_answer.yaml +29 -0
  231. data/data/matrix-spec/event-schemas/schema/m.direct.yaml +29 -0
  232. data/data/matrix-spec/event-schemas/schema/m.dummy.yaml +25 -0
  233. data/data/matrix-spec/event-schemas/schema/m.forwarded_room_key.yaml +70 -0
  234. data/data/matrix-spec/event-schemas/schema/m.fully_read.yaml +26 -0
  235. data/data/matrix-spec/event-schemas/schema/m.identity_server.yaml +28 -0
  236. data/data/matrix-spec/event-schemas/schema/m.ignored_user_list.yaml +30 -0
  237. data/data/matrix-spec/event-schemas/schema/m.invite_permission_config.yaml +21 -0
  238. data/data/matrix-spec/event-schemas/schema/m.key.verification.accept.yaml +63 -0
  239. data/data/matrix-spec/event-schemas/schema/m.key.verification.cancel.yaml +72 -0
  240. data/data/matrix-spec/event-schemas/schema/m.key.verification.done.yaml +24 -0
  241. data/data/matrix-spec/event-schemas/schema/m.key.verification.key.yaml +31 -0
  242. data/data/matrix-spec/event-schemas/schema/m.key.verification.m.relates_to.yaml +23 -0
  243. data/data/matrix-spec/event-schemas/schema/m.key.verification.mac.yaml +43 -0
  244. data/data/matrix-spec/event-schemas/schema/m.key.verification.ready.yaml +41 -0
  245. data/data/matrix-spec/event-schemas/schema/m.key.verification.request.yaml +47 -0
  246. data/data/matrix-spec/event-schemas/schema/m.key.verification.start$m.reciprocate.v1.yaml +45 -0
  247. data/data/matrix-spec/event-schemas/schema/m.key.verification.start$m.sas.v1.yaml +76 -0
  248. data/data/matrix-spec/event-schemas/schema/m.key.verification.start.yaml +46 -0
  249. data/data/matrix-spec/event-schemas/schema/m.key_backup.yaml +24 -0
  250. data/data/matrix-spec/event-schemas/schema/m.marked_unread.yaml +26 -0
  251. data/data/matrix-spec/event-schemas/schema/m.policy.rule.room.yaml +17 -0
  252. data/data/matrix-spec/event-schemas/schema/m.policy.rule.server.yaml +17 -0
  253. data/data/matrix-spec/event-schemas/schema/m.policy.rule.user.yaml +17 -0
  254. data/data/matrix-spec/event-schemas/schema/m.presence.yaml +50 -0
  255. data/data/matrix-spec/event-schemas/schema/m.push_rules.yaml +23 -0
  256. data/data/matrix-spec/event-schemas/schema/m.reaction.yaml +45 -0
  257. data/data/matrix-spec/event-schemas/schema/m.receipt.yaml +65 -0
  258. data/data/matrix-spec/event-schemas/schema/m.recent_emoji.yaml +29 -0
  259. data/data/matrix-spec/event-schemas/schema/m.room.avatar.yaml +29 -0
  260. data/data/matrix-spec/event-schemas/schema/m.room.canonical_alias.yaml +40 -0
  261. data/data/matrix-spec/event-schemas/schema/m.room.create.yaml +86 -0
  262. data/data/matrix-spec/event-schemas/schema/m.room.encrypted.yaml +91 -0
  263. data/data/matrix-spec/event-schemas/schema/m.room.encryption.yaml +38 -0
  264. data/data/matrix-spec/event-schemas/schema/m.room.guest_access.yaml +28 -0
  265. data/data/matrix-spec/event-schemas/schema/m.room.history_visibility.yaml +30 -0
  266. data/data/matrix-spec/event-schemas/schema/m.room.join_rules.yaml +78 -0
  267. data/data/matrix-spec/event-schemas/schema/m.room.member.yaml +174 -0
  268. data/data/matrix-spec/event-schemas/schema/m.room.message$m.audio.yaml +73 -0
  269. data/data/matrix-spec/event-schemas/schema/m.room.message$m.emote.yaml +36 -0
  270. data/data/matrix-spec/event-schemas/schema/m.room.message$m.file.yaml +85 -0
  271. data/data/matrix-spec/event-schemas/schema/m.room.message$m.image.yaml +63 -0
  272. data/data/matrix-spec/event-schemas/schema/m.room.message$m.key.verification.request.yaml +70 -0
  273. data/data/matrix-spec/event-schemas/schema/m.room.message$m.location.yaml +50 -0
  274. data/data/matrix-spec/event-schemas/schema/m.room.message$m.notice.yaml +36 -0
  275. data/data/matrix-spec/event-schemas/schema/m.room.message$m.server_notice.yaml +41 -0
  276. data/data/matrix-spec/event-schemas/schema/m.room.message$m.text.yaml +36 -0
  277. data/data/matrix-spec/event-schemas/schema/m.room.message$m.video.yaml +95 -0
  278. data/data/matrix-spec/event-schemas/schema/m.room.message.yaml +25 -0
  279. data/data/matrix-spec/event-schemas/schema/m.room.name.yaml +35 -0
  280. data/data/matrix-spec/event-schemas/schema/m.room.pinned_events.yaml +27 -0
  281. data/data/matrix-spec/event-schemas/schema/m.room.policy.yaml +41 -0
  282. data/data/matrix-spec/event-schemas/schema/m.room.power_levels.yaml +139 -0
  283. data/data/matrix-spec/event-schemas/schema/m.room.redaction.yaml +29 -0
  284. data/data/matrix-spec/event-schemas/schema/m.room.server_acl.yaml +88 -0
  285. data/data/matrix-spec/event-schemas/schema/m.room.third_party_invite.yaml +79 -0
  286. data/data/matrix-spec/event-schemas/schema/m.room.tombstone.yaml +29 -0
  287. data/data/matrix-spec/event-schemas/schema/m.room.topic.yaml +53 -0
  288. data/data/matrix-spec/event-schemas/schema/m.room_key.withheld.yaml +88 -0
  289. data/data/matrix-spec/event-schemas/schema/m.room_key.yaml +38 -0
  290. data/data/matrix-spec/event-schemas/schema/m.room_key_request.yaml +73 -0
  291. data/data/matrix-spec/event-schemas/schema/m.secret.request.yaml +42 -0
  292. data/data/matrix-spec/event-schemas/schema/m.secret.send.yaml +35 -0
  293. data/data/matrix-spec/event-schemas/schema/m.space.child.yaml +50 -0
  294. data/data/matrix-spec/event-schemas/schema/m.space.parent.yaml +36 -0
  295. data/data/matrix-spec/event-schemas/schema/m.sticker.yaml +36 -0
  296. data/data/matrix-spec/event-schemas/schema/m.tag.yaml +28 -0
  297. data/data/matrix-spec/event-schemas/schema/m.typing.yaml +29 -0
  298. data/lib/async/matrix/api/chain.rb +575 -0
  299. data/lib/async/matrix/api/concat.rb +105 -0
  300. data/lib/async/matrix/api/path_tree.rb +208 -0
  301. data/lib/async/matrix/api.rb +204 -0
  302. data/lib/async/matrix/application_service/bot.rb +235 -0
  303. data/lib/async/matrix/application_service/config/schema/analytics.json +21 -0
  304. data/lib/async/matrix/application_service/config/schema/appservice.json +82 -0
  305. data/lib/async/matrix/application_service/config/schema/backfill.json +91 -0
  306. data/lib/async/matrix/application_service/config/schema/bridge.json +209 -0
  307. data/lib/async/matrix/application_service/config/schema/config.json +61 -0
  308. data/lib/async/matrix/application_service/config/schema/database.json +38 -0
  309. data/lib/async/matrix/application_service/config/schema/direct_media.json +35 -0
  310. data/lib/async/matrix/application_service/config/schema/double_puppet.json +24 -0
  311. data/lib/async/matrix/application_service/config/schema/encryption.json +164 -0
  312. data/lib/async/matrix/application_service/config/schema/homeserver.json +58 -0
  313. data/lib/async/matrix/application_service/config/schema/logging.json +50 -0
  314. data/lib/async/matrix/application_service/config/schema/management_room_texts.json +25 -0
  315. data/lib/async/matrix/application_service/config/schema/matrix.json +45 -0
  316. data/lib/async/matrix/application_service/config/schema/permissions.json +54 -0
  317. data/lib/async/matrix/application_service/config/schema/provisioning.json +23 -0
  318. data/lib/async/matrix/application_service/config/schema/public_media.json +39 -0
  319. data/lib/async/matrix/application_service/config/schema/relay.json +43 -0
  320. data/lib/async/matrix/application_service/config/vivify.rb +110 -0
  321. data/lib/async/matrix/application_service/config.rb +214 -123
  322. data/lib/async/matrix/application_service/dispatcher.rb +115 -113
  323. data/lib/async/matrix/application_service/error_response.rb +26 -26
  324. data/lib/async/matrix/application_service/event.rb +217 -1
  325. data/lib/async/matrix/application_service/server.rb +286 -215
  326. data/lib/async/matrix/application_service/transaction.rb +52 -52
  327. data/lib/async/matrix/application_service/transaction_store.rb +62 -62
  328. data/lib/async/matrix/client.rb +919 -179
  329. data/lib/async/matrix/connection.rb +8 -8
  330. data/lib/async/matrix/endpoint.rb +45 -45
  331. data/lib/async/matrix/error.rb +49 -43
  332. data/lib/async/matrix/media_client.rb +173 -0
  333. data/lib/async/matrix/notifier.rb +92 -92
  334. data/lib/async/matrix/schema/registry.rb +355 -0
  335. data/lib/async/matrix/schema/validation_error.rb +226 -0
  336. data/lib/async/matrix/schema.rb +174 -0
  337. data/lib/async/matrix/server.rb +7 -7
  338. data/lib/async/matrix/stream.rb +7 -7
  339. data/lib/async/matrix/version.rb +1 -1
  340. data/lib/async/matrix.rb +5 -2
  341. metadata +353 -1
@@ -0,0 +1,575 @@
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 "bundler/setup"
7
+ require "async/matrix"
8
+ require "string_builder"
9
+ require "erb"
10
+
11
+ module Async
12
+ module Matrix
13
+ module Api
14
+ # Wraps a StringBuilder to build Matrix API paths via method chaining,
15
+ # terminated by .get(), .post(), .put(), or .delete() which validate
16
+ # the path against the PathTree and fire the HTTP request.
17
+ #
18
+ # Inherits from BasicObject so that method names like `send`, `display`,
19
+ # `format`, `test`, etc. are not defined and fall through to
20
+ # method_missing, where they get recorded as URL path segments.
21
+ #
22
+ # Usage:
23
+ # chain = Chain.new(client: client, path_tree: tree, prefix: %w[_matrix client v3])
24
+ # chain.account.whoami.get
25
+ # chain.rooms("!abc:ex.com").send("m.room.message", "txn1").put(body: "hi")
26
+ # chain.rooms("!abc:ex.com").messages.get(dir: "b", limit: 10)
27
+ #
28
+ class Chain < ::BasicObject
29
+ # Paths that carry raw bytes instead of JSON. Matched by
30
+ # _binary_route? where * is a single-segment wildcard.
31
+ # The HTTP method determines the operation:
32
+ # GET → download (returns raw bytes)
33
+ # POST/PUT → upload (sends raw bytes)
34
+ BINARY_ROUTES = [
35
+ "/_matrix/media/v3/upload",
36
+ "/_matrix/media/v3/upload/*/*",
37
+ "/_matrix/media/v3/download/*/*",
38
+ "/_matrix/media/v3/download/*/*/*",
39
+ "/_matrix/media/v3/thumbnail/*/*",
40
+ "/_matrix/client/v1/media/download/*/*",
41
+ "/_matrix/client/v1/media/download/*/*/*",
42
+ "/_matrix/client/v1/media/thumbnail/*/*",
43
+ ].freeze
44
+
45
+ def initialize(client:, path_tree:, prefix: nil)
46
+ @client = client
47
+ @path_tree = path_tree
48
+ @prefix = prefix || ["_matrix", "client", "v3"]
49
+ @buffer = []
50
+ end
51
+
52
+ # ── Terminal HTTP methods ──────────────────────────────────
53
+
54
+ def get(**kwargs)
55
+ max_retries = kwargs.delete(:max_retries)
56
+ execute("GET", query: kwargs, max_retries: max_retries)
57
+ end
58
+
59
+ def post(body = nil, **kwargs)
60
+ content_type = kwargs.delete(:content_type)
61
+ max_retries = kwargs.delete(:max_retries)
62
+ query = _extract_query_params(kwargs)
63
+ execute("POST", body: body || kwargs, content_type: content_type, max_retries: max_retries, query: query)
64
+ end
65
+
66
+ def put(body = nil, **kwargs)
67
+ content_type = kwargs.delete(:content_type)
68
+ max_retries = kwargs.delete(:max_retries)
69
+ query = _extract_query_params(kwargs)
70
+ execute("PUT", body: body || kwargs, content_type: content_type, max_retries: max_retries, query: query)
71
+ end
72
+
73
+ def delete(**kwargs)
74
+ max_retries = kwargs.delete(:max_retries)
75
+ execute("DELETE", query: kwargs, max_retries: max_retries)
76
+ end
77
+
78
+ # ── Chain inspection ───────────────────────────────────────
79
+
80
+ def to_s
81
+ "/" + _build_full_segments.map { |s| _encode(s) }.join("/")
82
+ end
83
+
84
+ def inspect
85
+ segments = _build_full_segments
86
+ "#<Async::Matrix::Api::Chain path=/#{segments.join("/")}>"
87
+ end
88
+
89
+ def path_segments
90
+ _build_full_segments
91
+ end
92
+
93
+ # ── Everything else becomes a path segment ─────────────────
94
+
95
+ def method_missing(name, *args, **kwargs)
96
+ if kwargs.empty?
97
+ @buffer << [name.to_s, args]
98
+ else
99
+ @buffer << [name.to_s, [*args, kwargs]]
100
+ end
101
+ self
102
+ end
103
+
104
+ private
105
+
106
+ def execute(method, body: nil, query: nil, content_type: nil, max_retries: nil)
107
+ segments = _rewrite_version(_build_full_segments)
108
+ result = @path_tree.match(segments, method)
109
+
110
+ if !result[:valid]
111
+ path = "/" + segments.join("/")
112
+ available = result[:methods]
113
+ if available.empty?
114
+ ::Kernel.raise ::Async::Matrix::InvalidEndpointError.new(
115
+ "INVALID_ENDPOINT",
116
+ "No Matrix API endpoint matches: #{method} #{path}"
117
+ )
118
+ else
119
+ ::Kernel.raise ::Async::Matrix::InvalidEndpointError.new(
120
+ "METHOD_NOT_ALLOWED",
121
+ "#{method} not allowed for #{path}. Valid methods: #{available.map(&:upcase).join(", ")}"
122
+ )
123
+ end
124
+ end
125
+
126
+ path = "/" + segments.map { |s| _encode(s) }.join("/")
127
+
128
+ if _binary_route?(segments)
129
+ if query && !query.empty?
130
+ qs = query.map { |k, v| "#{_encode(k.to_s)}=#{_encode(v.to_s)}" }.join("&")
131
+ path = "#{path}?#{qs}"
132
+ end
133
+ case method
134
+ when "GET"
135
+ @client.media_client.download(path)
136
+ when "POST"
137
+ @client.media_client.upload("POST", path, body, content_type || "application/octet-stream")
138
+ when "PUT"
139
+ @client.media_client.upload("PUT", path, body, content_type || "application/octet-stream")
140
+ end
141
+ else
142
+ retry_opts = max_retries ? {max_retries: max_retries} : {}
143
+ case method
144
+ when "GET"
145
+ if query && !query.empty?
146
+ qs = query.map { |k, v| "#{_encode(k.to_s)}=#{_encode(v.to_s)}" }.join("&")
147
+ path = "#{path}?#{qs}"
148
+ end
149
+ @client.get(path, **retry_opts)
150
+ when "POST"
151
+ @client.post(path, body || {}, **retry_opts)
152
+ when "PUT"
153
+ @client.put(path, body || {}, **retry_opts)
154
+ when "DELETE"
155
+ @client.request("DELETE", path, nil, **retry_opts)
156
+ end
157
+ end
158
+ end
159
+
160
+ # Rewrites version segments for endpoints that only exist at
161
+ # a different version than the gateway prefix provides.
162
+ # _matrix/media/v3/create → _matrix/media/v1/create
163
+ # _matrix/client/v3/media/... → _matrix/client/v1/media/...
164
+ def _rewrite_version(segments)
165
+ # POST /_matrix/media/v1/create (only exists at v1)
166
+ if segments.length == 4 &&
167
+ segments[0] == "_matrix" && segments[1] == "media" &&
168
+ segments[2] == "v3" && segments[3] == "create"
169
+ segments = segments.dup
170
+ segments[2] = "v1"
171
+ return segments
172
+ end
173
+
174
+ # Authenticated media endpoints live at /_matrix/client/v1/media/...
175
+ if segments.length >= 5 &&
176
+ segments[0] == "_matrix" && segments[1] == "client" &&
177
+ segments[2] == "v3" && segments[3] == "media"
178
+ segments = segments.dup
179
+ segments[2] = "v1"
180
+ return segments
181
+ end
182
+
183
+ segments
184
+ end
185
+
186
+ def _binary_route?(segments)
187
+ BINARY_ROUTES.any? do |pattern|
188
+ parts = pattern.split("/")
189
+ next false unless parts.length == segments.length + 1 # leading slash adds empty element
190
+ parts.shift # remove empty string from leading /
191
+ parts.length == segments.length &&
192
+ parts.zip(segments).all? { |pat, seg| pat == "*" || pat == seg }
193
+ end
194
+ end
195
+
196
+ def _extract_query_params(kwargs)
197
+ query = {}
198
+ kwargs.each_key do |k|
199
+ key_s = k.to_s
200
+ if key_s.start_with?("?")
201
+ query[key_s[1..]] = kwargs.delete(k)
202
+ end
203
+ end
204
+ query.empty? ? nil : query
205
+ end
206
+
207
+ def _build_full_segments
208
+ chain_segments = []
209
+ @buffer.each do |name, args|
210
+ chain_segments << name
211
+ next if args.nil? || args.empty?
212
+ args.each do |arg|
213
+ next if arg.is_a?(::Hash)
214
+ chain_segments << arg.to_s
215
+ end
216
+ end
217
+ @prefix + chain_segments
218
+ end
219
+
220
+ def _encode(value)
221
+ ::ERB::Util.url_encode(value.to_s)
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ test do
229
+ describe "Async::Matrix::Api::Chain" do
230
+ def make_tree
231
+ tree = Async::Matrix::Api::PathTree.new
232
+ tree.insert(%w[_matrix client v3 account whoami], "get", "getAccountWhoami")
233
+ tree.insert(%w[_matrix client v3 createRoom], "post", "createRoom")
234
+ tree.insert(%w[_matrix client v3 rooms {roomId} ban], "post", "ban")
235
+ tree.insert(%w[_matrix client v3 rooms {roomId} state {eventType} {stateKey}], "get")
236
+ tree.insert(%w[_matrix client v3 rooms {roomId} state {eventType} {stateKey}], "put")
237
+ tree.insert(%w[_matrix client v3 rooms {roomId} messages], "get", "getRoomMessages")
238
+ tree.insert(%w[_matrix client v3 rooms {roomId} send {eventType} {txnId}], "put")
239
+ tree.insert(%w[_matrix client v3 profile {userId} displayname], "put", "setDisplayName")
240
+ tree
241
+ end
242
+
243
+ it "builds correct path segments for simple chain" do
244
+ chain = Async::Matrix::Api::Chain.new(
245
+ client: nil, path_tree: make_tree
246
+ )
247
+ chain.account.whoami
248
+ chain.path_segments.should == %w[_matrix client v3 account whoami]
249
+ end
250
+
251
+ it "builds correct path segments with args" do
252
+ chain = Async::Matrix::Api::Chain.new(
253
+ client: nil, path_tree: make_tree
254
+ )
255
+ chain.rooms("!abc:ex.com").ban
256
+ chain.path_segments.should == %w[_matrix client v3 rooms !abc:ex.com ban]
257
+ end
258
+
259
+ it "builds correct to_s with URL encoding" do
260
+ chain = Async::Matrix::Api::Chain.new(
261
+ client: nil, path_tree: make_tree
262
+ )
263
+ chain.rooms("!abc:ex.com").ban
264
+ chain.to_s.should == "/_matrix/client/v3/rooms/%21abc%3Aex.com/ban"
265
+ end
266
+
267
+ it "builds path for /rooms/{roomId}/send/{eventType}/{txnId}" do
268
+ chain = Async::Matrix::Api::Chain.new(
269
+ client: nil, path_tree: make_tree
270
+ )
271
+ chain.rooms("!abc:ex.com").send("m.room.message", "txn-123")
272
+ chain.path_segments.should == %w[_matrix client v3 rooms !abc:ex.com send m.room.message txn-123]
273
+ end
274
+
275
+ it "raises InvalidEndpointError for unknown path" do
276
+ chain = Async::Matrix::Api::Chain.new(
277
+ client: nil, path_tree: make_tree
278
+ )
279
+ chain.account.foobar
280
+ lambda { chain.get }.should.raise Async::Matrix::InvalidEndpointError
281
+ end
282
+
283
+ it "raises InvalidEndpointError for wrong HTTP method" do
284
+ chain = Async::Matrix::Api::Chain.new(
285
+ client: nil, path_tree: make_tree
286
+ )
287
+ chain.account.whoami
288
+ lambda { chain.post }.should.raise Async::Matrix::InvalidEndpointError
289
+ end
290
+
291
+ # ── Binary route detection ───────────────────────────────
292
+
293
+ def make_media_tree
294
+ tree = Async::Matrix::Api::PathTree.new
295
+ tree.insert(%w[_matrix media v3 upload], "post", "uploadContent")
296
+ tree.insert(%w[_matrix media v3 upload {serverName} {mediaId}], "put", "uploadContentToMXC")
297
+ tree.insert(%w[_matrix media v3 download {serverName} {mediaId}], "get", "getContent")
298
+ tree.insert(%w[_matrix media v3 download {serverName} {mediaId} {fileName}], "get", "getContentOverrideName")
299
+ tree.insert(%w[_matrix media v3 thumbnail {serverName} {mediaId}], "get", "getContentThumbnail")
300
+ tree.insert(%w[_matrix client v1 media download {serverName} {mediaId}], "get", "getContentAuthed")
301
+ tree.insert(%w[_matrix client v1 media download {serverName} {mediaId} {fileName}], "get", "getContentOverrideNameAuthed")
302
+ tree.insert(%w[_matrix client v1 media thumbnail {serverName} {mediaId}], "get", "getContentThumbnailAuthed")
303
+ tree
304
+ end
305
+
306
+ # Stub response object for download tests.
307
+ StubResponse = Struct.new(:body_bytes, :headers, :status) do
308
+ def initialize(body_bytes = "\xFF\xD8\xFF".b)
309
+ super(body_bytes, {"content-type" => "image/jpeg"}, 200)
310
+ end
311
+
312
+ def read
313
+ body_bytes
314
+ end
315
+ end
316
+
317
+ # Stub client with a stub media_client for testing dispatch.
318
+ StubMediaClient = Struct.new(:calls) do
319
+ def initialize
320
+ super([])
321
+ end
322
+
323
+ def upload(method, path, body, content_type)
324
+ calls << [:upload, method, path, body, content_type]
325
+ {"content_uri" => "mxc://example.com/abc123"}
326
+ end
327
+
328
+ def download(path)
329
+ calls << [:download, path]
330
+ StubResponse.new
331
+ end
332
+ end
333
+
334
+ StubClientWithMedia = Struct.new(:calls, :media_client) do
335
+ def initialize
336
+ super([], StubMediaClient.new)
337
+ end
338
+
339
+ def get(path, max_retries: nil)
340
+ calls << [:get, path, {max_retries: max_retries}]
341
+ {"stub" => true}
342
+ end
343
+
344
+ def post(path, body = {}, max_retries: nil)
345
+ calls << [:post, path, body, {max_retries: max_retries}]
346
+ {"stub" => true}
347
+ end
348
+
349
+ def put(path, body = {}, max_retries: nil)
350
+ calls << [:put, path, body, {max_retries: max_retries}]
351
+ {"stub" => true}
352
+ end
353
+
354
+ def request(method, path, body = nil, max_retries: nil)
355
+ calls << [method.downcase.to_sym, path, body, {max_retries: max_retries}]
356
+ {"stub" => true}
357
+ end
358
+ end
359
+
360
+ it "detects upload route and dispatches to media_client" do
361
+ client = StubClientWithMedia.new
362
+ chain = Async::Matrix::Api::Chain.new(
363
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
364
+ )
365
+ chain.upload.post("image-bytes", content_type: "image/png")
366
+ client.media_client.calls.last[0].should == :upload
367
+ client.media_client.calls.last[1].should == "POST"
368
+ client.media_client.calls.last[3].should == "image-bytes"
369
+ client.media_client.calls.last[4].should == "image/png"
370
+ client.calls.should.be.empty
371
+ end
372
+
373
+ it "detects upload PUT route and dispatches to media_client" do
374
+ client = StubClientWithMedia.new
375
+ chain = Async::Matrix::Api::Chain.new(
376
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
377
+ )
378
+ chain.upload("example.com", "abc123").put("image-bytes", content_type: "image/jpeg")
379
+ client.media_client.calls.last[0].should == :upload
380
+ client.media_client.calls.last[1].should == "PUT"
381
+ client.media_client.calls.last[4].should == "image/jpeg"
382
+ client.calls.should.be.empty
383
+ end
384
+
385
+ it "detects download route and dispatches to media_client" do
386
+ client = StubClientWithMedia.new
387
+ chain = Async::Matrix::Api::Chain.new(
388
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
389
+ )
390
+ response = chain.download("example.com", "abc123").get
391
+ client.media_client.calls.last[0].should == :download
392
+ response.read.should == "\xFF\xD8\xFF".b
393
+ response.headers["content-type"].should == "image/jpeg"
394
+ client.calls.should.be.empty
395
+ end
396
+
397
+ it "detects download with filename route" do
398
+ client = StubClientWithMedia.new
399
+ chain = Async::Matrix::Api::Chain.new(
400
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
401
+ )
402
+ chain.download("example.com", "abc123", "photo.jpg").get
403
+ client.media_client.calls.last[0].should == :download
404
+ end
405
+
406
+ it "detects thumbnail route and passes query params" do
407
+ client = StubClientWithMedia.new
408
+ chain = Async::Matrix::Api::Chain.new(
409
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
410
+ )
411
+ chain.thumbnail("example.com", "abc123").get(width: 64, height: 64)
412
+ call = client.media_client.calls.last
413
+ call[0].should == :download
414
+ call[1].should.include "width=64"
415
+ call[1].should.include "height=64"
416
+ end
417
+
418
+ it "detects authed download route (client/v1 prefix)" do
419
+ client = StubClientWithMedia.new
420
+ chain = Async::Matrix::Api::Chain.new(
421
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix client v1]
422
+ )
423
+ chain.media.download("example.com", "abc123").get
424
+ client.media_client.calls.last[0].should == :download
425
+ end
426
+
427
+ it "does NOT route non-binary paths to media_client" do
428
+ client = StubClientWithMedia.new
429
+ tree = make_tree
430
+ chain = Async::Matrix::Api::Chain.new(
431
+ client: client, path_tree: tree
432
+ )
433
+ chain.account.whoami.get
434
+ client.calls.last[0].should == :get
435
+ client.media_client.calls.should.be.empty
436
+ end
437
+
438
+ it "defaults content_type to application/octet-stream for uploads" do
439
+ client = StubClientWithMedia.new
440
+ chain = Async::Matrix::Api::Chain.new(
441
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
442
+ )
443
+ chain.upload.post("raw-bytes")
444
+ client.media_client.calls.last[4].should == "application/octet-stream"
445
+ end
446
+
447
+ # ── ?-prefixed query params on uploads ────────────────────
448
+
449
+ it "passes ?-prefixed kwargs as query params on upload POST" do
450
+ client = StubClientWithMedia.new
451
+ chain = Async::Matrix::Api::Chain.new(
452
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
453
+ )
454
+ chain.upload.post("image-bytes", content_type: "image/png", "?filename": "photo.png")
455
+ call = client.media_client.calls.last
456
+ call[0].should == :upload
457
+ call[2].should.include "filename=photo.png"
458
+ call[4].should == "image/png"
459
+ end
460
+
461
+ it "passes ?-prefixed kwargs as query params on upload PUT" do
462
+ client = StubClientWithMedia.new
463
+ chain = Async::Matrix::Api::Chain.new(
464
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix media v3]
465
+ )
466
+ chain.upload("example.com", "abc123").put("bytes", content_type: "image/jpeg", "?filename": "pic.jpg")
467
+ call = client.media_client.calls.last
468
+ call[0].should == :upload
469
+ call[2].should.include "filename=pic.jpg"
470
+ end
471
+
472
+ # ── Version rewrites ──────────────────────────────────────
473
+
474
+ def make_full_media_tree
475
+ tree = make_media_tree
476
+ tree.insert(%w[_matrix media v1 create], "post", "createContent")
477
+ tree.insert(%w[_matrix media v3 config], "get", "getConfig")
478
+ tree.insert(%w[_matrix media v3 preview_url], "get", "getUrlPreview")
479
+ tree
480
+ end
481
+
482
+ it "rewrites media/v3/create to media/v1/create" do
483
+ client = StubClientWithMedia.new
484
+ chain = Async::Matrix::Api::Chain.new(
485
+ client: client, path_tree: make_full_media_tree, prefix: %w[_matrix media v3]
486
+ )
487
+ chain.create.post
488
+ call = client.calls.last
489
+ call[0].should == :post
490
+ call[1].should == "/_matrix/media/v1/create"
491
+ end
492
+
493
+ it "rewrites client/v3/media/download to client/v1/media/download" do
494
+ client = StubClientWithMedia.new
495
+ chain = Async::Matrix::Api::Chain.new(
496
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix client v3]
497
+ )
498
+ chain.media.download("example.com", "abc123").get
499
+ call = client.media_client.calls.last
500
+ call[0].should == :download
501
+ call[1].should.include "/_matrix/client/v1/media/download/"
502
+ end
503
+
504
+ it "rewrites client/v3/media/thumbnail to client/v1/media/thumbnail" do
505
+ client = StubClientWithMedia.new
506
+ chain = Async::Matrix::Api::Chain.new(
507
+ client: client, path_tree: make_media_tree, prefix: %w[_matrix client v3]
508
+ )
509
+ chain.media.thumbnail("example.com", "abc123").get(width: 32, height: 32)
510
+ call = client.media_client.calls.last
511
+ call[0].should == :download
512
+ call[1].should.include "/_matrix/client/v1/media/thumbnail/"
513
+ end
514
+
515
+ it "does NOT rewrite non-media client/v3 paths" do
516
+ client = StubClientWithMedia.new
517
+ tree = make_tree
518
+ chain = Async::Matrix::Api::Chain.new(
519
+ client: client, path_tree: tree, prefix: %w[_matrix client v3]
520
+ )
521
+ chain.account.whoami.get
522
+ call = client.calls.last
523
+ call[1].should == "/_matrix/client/v3/account/whoami"
524
+ end
525
+
526
+ it "does NOT rewrite media/v3 paths that are not create" do
527
+ client = StubClientWithMedia.new
528
+ chain = Async::Matrix::Api::Chain.new(
529
+ client: client, path_tree: make_full_media_tree, prefix: %w[_matrix media v3]
530
+ )
531
+ chain.config.get
532
+ call = client.calls.last
533
+ call[0].should == :get
534
+ call[1].should == "/_matrix/media/v3/config"
535
+ end
536
+
537
+ # ── max_retries forwarding ─────────────────────────────────
538
+
539
+ it "forwards max_retries on get" do
540
+ client = StubClientWithMedia.new
541
+ chain = Async::Matrix::Api::Chain.new(
542
+ client: client, path_tree: make_tree
543
+ )
544
+ chain.account.whoami.get(max_retries: 0)
545
+ client.calls.last.last[:max_retries].should == 0
546
+ end
547
+
548
+ it "forwards max_retries on post" do
549
+ client = StubClientWithMedia.new
550
+ chain = Async::Matrix::Api::Chain.new(
551
+ client: client, path_tree: make_tree
552
+ )
553
+ chain.createRoom.post({name: "Test"}, max_retries: 1)
554
+ client.calls.last.last[:max_retries].should == 1
555
+ end
556
+
557
+ it "forwards max_retries on put" do
558
+ client = StubClientWithMedia.new
559
+ chain = Async::Matrix::Api::Chain.new(
560
+ client: client, path_tree: make_tree
561
+ )
562
+ chain.profile("@user:ex.com").displayname.put({displayname: "Bob"}, max_retries: 2)
563
+ client.calls.last.last[:max_retries].should == 2
564
+ end
565
+
566
+ it "forwards nil max_retries by default" do
567
+ client = StubClientWithMedia.new
568
+ chain = Async::Matrix::Api::Chain.new(
569
+ client: client, path_tree: make_tree
570
+ )
571
+ chain.account.whoami.get
572
+ client.calls.last.last[:max_retries].should.be.nil
573
+ end
574
+ end
575
+ end
@@ -0,0 +1,105 @@
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 "bundler/setup"
7
+ require "async/matrix"
8
+
9
+ module Async
10
+ module Matrix
11
+ module Api
12
+ # StringBuilder concat handler that renders a method chain into URL path segments.
13
+ #
14
+ # Convention:
15
+ # - Bare methods become path segments: .account.whoami -> ["account", "whoami"]
16
+ # - Methods with args inject the arg as a path segment after the method name:
17
+ # .rooms("!abc:ex.com") -> ["rooms", "!abc:ex.com"]
18
+ # - .call("literal") injects a raw path segment:
19
+ # .("m.room.message") -> ["m.room.message"]
20
+ #
21
+ # The handler returns an array of raw (unencoded) segments.
22
+ # URL-encoding is applied later when constructing the final URL.
23
+ #
24
+ class Concat
25
+ def self.call(buffer)
26
+ new(buffer).segments
27
+ end
28
+
29
+ def initialize(buffer)
30
+ @buffer = buffer.respond_to?(:to_a) ? buffer.to_a : buffer
31
+ end
32
+
33
+ def segments
34
+ result = []
35
+
36
+ @buffer.each do |entry|
37
+ case entry
38
+ when Symbol
39
+ # :slash, :dash -- ignore in URL context
40
+ next
41
+ when Array
42
+ name, args = entry
43
+ next unless name.is_a?(String)
44
+
45
+ if args.nil? || args.empty?
46
+ # Bare method -> path segment
47
+ result << name
48
+ else
49
+ # Method with args -> method name as segment, then each arg as segment
50
+ result << name
51
+ args.each do |arg|
52
+ next if arg.is_a?(Hash) # kwargs are not path segments
53
+ result << arg.to_s
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ result
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ test do
67
+ require "string_builder"
68
+
69
+ describe "Async::Matrix::Api::Concat" do
70
+ it "converts bare methods to path segments" do
71
+ sb = StringBuilder.new
72
+ sb.account.whoami
73
+ segments = Async::Matrix::Api::Concat.call(sb)
74
+ segments.should == %w[account whoami]
75
+ end
76
+
77
+ it "converts methods with args to segment + arg" do
78
+ sb = StringBuilder.new
79
+ sb.rooms("!abc:ex.com").ban
80
+ segments = Async::Matrix::Api::Concat.call(sb)
81
+ segments.should == %w[rooms !abc:ex.com ban]
82
+ end
83
+
84
+ it "handles methods with multiple args" do
85
+ sb = StringBuilder.new
86
+ sb.rooms("!abc:ex.com").state("m.room.name", "some_key")
87
+ segments = Async::Matrix::Api::Concat.call(sb)
88
+ segments.should == %w[rooms !abc:ex.com state m.room.name some_key]
89
+ end
90
+
91
+ it "handles deep paths" do
92
+ sb = StringBuilder.new
93
+ sb.rooms("!abc:ex.com").state("m.room.name", "")
94
+ segments = Async::Matrix::Api::Concat.call(sb)
95
+ segments.should == ["rooms", "!abc:ex.com", "state", "m.room.name", ""]
96
+ end
97
+
98
+ it "ignores kwargs in segments" do
99
+ sb = StringBuilder.new
100
+ sb.account.whoami
101
+ segments = Async::Matrix::Api::Concat.call(sb)
102
+ segments.should == %w[account whoami]
103
+ end
104
+ end
105
+ end