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