onyxcord 2.1.0 → 3.2.1

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/lib/onyxcord/application_commands/command.rb +4 -97
  3. data/lib/onyxcord/application_commands/context.rb +4 -94
  4. data/lib/onyxcord/application_commands/option.rb +4 -75
  5. data/lib/onyxcord/application_commands/registry.rb +4 -45
  6. data/lib/onyxcord/application_commands.rb +8 -5
  7. data/lib/onyxcord/bot.rb +24 -1759
  8. data/lib/onyxcord/{cache.rb → cache/manager.rb} +16 -14
  9. data/lib/onyxcord/cache/stores/gateway.rb +272 -0
  10. data/lib/onyxcord/commands/bot/channels.rb +48 -0
  11. data/lib/onyxcord/commands/bot/execution.rb +174 -0
  12. data/lib/onyxcord/commands/bot/message_dispatch.rb +58 -0
  13. data/lib/onyxcord/commands/bot/permissions.rb +72 -0
  14. data/lib/onyxcord/commands/bot.rb +209 -0
  15. data/lib/onyxcord/commands/container.rb +21 -2
  16. data/lib/onyxcord/commands/parser.rb +30 -1
  17. data/lib/onyxcord/container.rb +16 -960
  18. data/lib/onyxcord/core/bot/application_commands.rb +210 -0
  19. data/lib/onyxcord/core/bot/awaits.rb +112 -0
  20. data/lib/onyxcord/core/bot/invites.rb +42 -0
  21. data/lib/onyxcord/core/bot/messaging.rb +161 -0
  22. data/lib/onyxcord/core/bot/oauth.rb +27 -0
  23. data/lib/onyxcord/core/bot/presence.rb +103 -0
  24. data/lib/onyxcord/core/bot/runtime.rb +183 -0
  25. data/lib/onyxcord/core/bot/voice.rb +69 -0
  26. data/lib/onyxcord/{logger.rb → core/logger.rb} +5 -0
  27. data/lib/onyxcord/core/version.rb +5 -0
  28. data/lib/onyxcord/events/await/base.rb +48 -0
  29. data/lib/onyxcord/events/await.rb +1 -46
  30. data/lib/onyxcord/events/ban/base.rb +54 -0
  31. data/lib/onyxcord/events/ban/remove.rb +11 -0
  32. data/lib/onyxcord/events/bans.rb +2 -58
  33. data/lib/onyxcord/events/channels/base.rb +91 -0
  34. data/lib/onyxcord/events/channels/delete.rb +73 -0
  35. data/lib/onyxcord/events/channels/pins.rb +44 -0
  36. data/lib/onyxcord/events/channels/recipients.rb +77 -0
  37. data/lib/onyxcord/events/channels.rb +4 -239
  38. data/lib/onyxcord/events/guilds/audit_logs.rb +79 -0
  39. data/lib/onyxcord/events/guilds/base.rb +97 -0
  40. data/lib/onyxcord/events/guilds/emoji.rb +147 -0
  41. data/lib/onyxcord/events/guilds.rb +3 -267
  42. data/lib/onyxcord/events/handlers/channels.rb +133 -0
  43. data/lib/onyxcord/events/handlers/core.rb +146 -0
  44. data/lib/onyxcord/events/handlers/guilds.rb +375 -0
  45. data/lib/onyxcord/events/handlers/interactions.rb +238 -0
  46. data/lib/onyxcord/events/handlers/lifetime.rb +71 -0
  47. data/lib/onyxcord/events/handlers/messages.rb +218 -0
  48. data/lib/onyxcord/events/handlers/presence.rb +56 -0
  49. data/lib/onyxcord/events/handlers/raw.rb +39 -0
  50. data/lib/onyxcord/events/handlers/reactions.rb +98 -0
  51. data/lib/onyxcord/events/handlers/scheduled_events.rb +243 -0
  52. data/lib/onyxcord/events/handlers/voice.rb +46 -0
  53. data/lib/onyxcord/events/integration/base.rb +45 -0
  54. data/lib/onyxcord/events/integration/create.rb +11 -0
  55. data/lib/onyxcord/events/integration/delete.rb +48 -0
  56. data/lib/onyxcord/events/integration/update.rb +11 -0
  57. data/lib/onyxcord/events/integrations.rb +4 -98
  58. data/lib/onyxcord/events/interactions/application_commands.rb +185 -0
  59. data/lib/onyxcord/events/interactions/autocomplete.rb +62 -0
  60. data/lib/onyxcord/events/interactions/base.rb +175 -0
  61. data/lib/onyxcord/events/interactions/components.rb +221 -0
  62. data/lib/onyxcord/events/interactions/permissions.rb +53 -0
  63. data/lib/onyxcord/events/interactions.rb +5 -622
  64. data/lib/onyxcord/events/invite/create.rb +79 -0
  65. data/lib/onyxcord/events/invite/delete.rb +55 -0
  66. data/lib/onyxcord/events/invites.rb +2 -125
  67. data/lib/onyxcord/events/lifetime/base.rb +31 -0
  68. data/lib/onyxcord/events/lifetime.rb +1 -29
  69. data/lib/onyxcord/events/member/add.rb +12 -0
  70. data/lib/onyxcord/events/member/base.rb +67 -0
  71. data/lib/onyxcord/events/member/delete.rb +21 -0
  72. data/lib/onyxcord/events/member/update.rb +25 -0
  73. data/lib/onyxcord/events/members.rb +4 -108
  74. data/lib/onyxcord/events/message/base.rb +295 -0
  75. data/lib/onyxcord/events/message/ids.rb +71 -0
  76. data/lib/onyxcord/events/message/mentions.rb +55 -0
  77. data/lib/onyxcord/events/message/updates.rb +27 -0
  78. data/lib/onyxcord/events/message.rb +4 -397
  79. data/lib/onyxcord/events/poll/add.rb +11 -0
  80. data/lib/onyxcord/events/poll/base.rb +106 -0
  81. data/lib/onyxcord/events/poll/remove.rb +11 -0
  82. data/lib/onyxcord/events/polls.rb +3 -116
  83. data/lib/onyxcord/events/presence/playing.rb +77 -0
  84. data/lib/onyxcord/events/presence/status.rb +60 -0
  85. data/lib/onyxcord/events/presence.rb +2 -129
  86. data/lib/onyxcord/events/raw/base.rb +74 -0
  87. data/lib/onyxcord/events/raw.rb +1 -72
  88. data/lib/onyxcord/events/reactions/base.rb +143 -0
  89. data/lib/onyxcord/events/reactions/clear.rb +99 -0
  90. data/lib/onyxcord/events/reactions.rb +2 -216
  91. data/lib/onyxcord/events/role/create.rb +46 -0
  92. data/lib/onyxcord/events/role/delete.rb +40 -0
  93. data/lib/onyxcord/events/role/update.rb +11 -0
  94. data/lib/onyxcord/events/roles.rb +3 -85
  95. data/lib/onyxcord/events/scheduled_events/events.rb +112 -0
  96. data/lib/onyxcord/events/scheduled_events/users.rb +91 -0
  97. data/lib/onyxcord/events/scheduled_events.rb +2 -169
  98. data/lib/onyxcord/events/thread/base.rb +49 -0
  99. data/lib/onyxcord/events/thread/members.rb +44 -0
  100. data/lib/onyxcord/events/thread/update.rb +18 -0
  101. data/lib/onyxcord/events/threads.rb +3 -98
  102. data/lib/onyxcord/events/typing/base.rb +73 -0
  103. data/lib/onyxcord/events/typing.rb +1 -71
  104. data/lib/onyxcord/events/voice/server_update.rb +48 -0
  105. data/lib/onyxcord/events/voice/state_update.rb +137 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +2 -46
  107. data/lib/onyxcord/events/voice_state_update.rb +2 -135
  108. data/lib/onyxcord/events/webhook/update.rb +65 -0
  109. data/lib/onyxcord/events/webhooks.rb +1 -63
  110. data/lib/onyxcord/gateway/client.rb +460 -0
  111. data/lib/onyxcord/interactions/command.rb +120 -0
  112. data/lib/onyxcord/interactions/context.rb +109 -0
  113. data/lib/onyxcord/interactions/internal/application_command.rb +203 -0
  114. data/lib/onyxcord/interactions/internal/message.rb +152 -0
  115. data/lib/onyxcord/interactions/internal/metadata.rb +109 -0
  116. data/lib/onyxcord/interactions/internal/option_builder.rb +230 -0
  117. data/lib/onyxcord/interactions/internal/permission_builder.rb +90 -0
  118. data/lib/onyxcord/interactions/option.rb +88 -0
  119. data/lib/onyxcord/interactions/registry.rb +50 -0
  120. data/lib/onyxcord/interactions.rb +16 -0
  121. data/lib/onyxcord/internal/async_runtime.rb +32 -0
  122. data/lib/onyxcord/internal/event_bus.rb +591 -0
  123. data/lib/onyxcord/internal/event_executor.rb +155 -0
  124. data/lib/onyxcord/internal/gateway/opcodes.rb +22 -0
  125. data/lib/onyxcord/internal/gateway/session.rb +45 -0
  126. data/lib/onyxcord/internal/http.rb +189 -0
  127. data/lib/onyxcord/internal/json.rb +51 -0
  128. data/lib/onyxcord/{message_payload.rb → internal/message_payload.rb} +3 -3
  129. data/lib/onyxcord/internal/rate_limiter/async_rest.rb +151 -0
  130. data/lib/onyxcord/internal/rate_limiter/gateway.rb +44 -0
  131. data/lib/onyxcord/internal/rate_limiter/rest.rb +144 -0
  132. data/lib/onyxcord/internal/websocket.rb +79 -0
  133. data/lib/onyxcord/light/data.rb +1 -1
  134. data/lib/onyxcord/light/integrations.rb +1 -1
  135. data/lib/onyxcord/light/light_bot.rb +7 -7
  136. data/lib/onyxcord/{data → models}/activity.rb +2 -2
  137. data/lib/onyxcord/{data → models}/application.rb +3 -3
  138. data/lib/onyxcord/{data → models}/avatar_decoration.rb +1 -1
  139. data/lib/onyxcord/{data → models}/channel.rb +23 -23
  140. data/lib/onyxcord/{data → models}/collectibles.rb +1 -1
  141. data/lib/onyxcord/{data → models}/emoji.rb +1 -1
  142. data/lib/onyxcord/models/interaction.rb +450 -0
  143. data/lib/onyxcord/{data → models}/invite.rb +1 -1
  144. data/lib/onyxcord/{data → models}/member.rb +11 -11
  145. data/lib/onyxcord/{data → models}/message.rb +18 -14
  146. data/lib/onyxcord/{data → models}/poll.rb +2 -2
  147. data/lib/onyxcord/{data → models}/primary_server.rb +1 -1
  148. data/lib/onyxcord/{data → models}/profile.rb +4 -4
  149. data/lib/onyxcord/{data → models}/role.rb +6 -12
  150. data/lib/onyxcord/{data → models}/scheduled_event.rb +5 -5
  151. data/lib/onyxcord/{data → models}/server.rb +41 -41
  152. data/lib/onyxcord/{data → models}/server_preview.rb +2 -2
  153. data/lib/onyxcord/{data → models}/team.rb +1 -1
  154. data/lib/onyxcord/{data → models}/user.rb +5 -5
  155. data/lib/onyxcord/{data → models}/webhook.rb +10 -10
  156. data/lib/onyxcord/models.rb +57 -0
  157. data/lib/onyxcord/{api.rb → rest/client.rb} +23 -22
  158. data/lib/onyxcord/{api → rest/routes}/application.rb +79 -57
  159. data/lib/onyxcord/rest/routes/channel/base.rb +73 -0
  160. data/lib/onyxcord/rest/routes/channel/groups.rb +78 -0
  161. data/lib/onyxcord/rest/routes/channel/messages.rb +130 -0
  162. data/lib/onyxcord/rest/routes/channel/permissions.rb +73 -0
  163. data/lib/onyxcord/rest/routes/channel/pins.rb +45 -0
  164. data/lib/onyxcord/rest/routes/channel/polls.rb +32 -0
  165. data/lib/onyxcord/rest/routes/channel/reactions.rb +86 -0
  166. data/lib/onyxcord/rest/routes/channel/threads.rb +176 -0
  167. data/lib/onyxcord/rest/routes/channel/webhooks.rb +47 -0
  168. data/lib/onyxcord/rest/routes/channel.rb +12 -0
  169. data/lib/onyxcord/{api/interaction.rb → rest/routes/interaction/base.rb} +7 -25
  170. data/lib/onyxcord/rest/routes/interaction/response.rb +24 -0
  171. data/lib/onyxcord/rest/routes/interaction.rb +4 -0
  172. data/lib/onyxcord/{api → rest/routes}/invite.rb +7 -7
  173. data/lib/onyxcord/rest/routes/server/admin.rb +174 -0
  174. data/lib/onyxcord/rest/routes/server/assets.rb +25 -0
  175. data/lib/onyxcord/rest/routes/server/bans.rb +67 -0
  176. data/lib/onyxcord/rest/routes/server/base.rb +77 -0
  177. data/lib/onyxcord/rest/routes/server/channels.rb +58 -0
  178. data/lib/onyxcord/rest/routes/server/emoji.rb +48 -0
  179. data/lib/onyxcord/rest/routes/server/members.rb +109 -0
  180. data/lib/onyxcord/rest/routes/server/messages.rb +19 -0
  181. data/lib/onyxcord/rest/routes/server/roles.rb +156 -0
  182. data/lib/onyxcord/rest/routes/server/scheduled_events.rb +86 -0
  183. data/lib/onyxcord/rest/routes/server.rb +12 -0
  184. data/lib/onyxcord/{api → rest/routes}/user.rb +23 -23
  185. data/lib/onyxcord/{api → rest/routes}/webhook.rb +22 -22
  186. data/lib/onyxcord/{allowed_mentions.rb → utils/allowed_mentions.rb} +1 -1
  187. data/lib/onyxcord/utils/timestamp.rb +69 -0
  188. data/lib/onyxcord/voice/{voice_bot.rb → client.rb} +4 -4
  189. data/lib/onyxcord/voice/encoder.rb +1 -1
  190. data/lib/onyxcord/voice/network/udp.rb +135 -0
  191. data/lib/onyxcord/voice/network/websocket.rb +225 -0
  192. data/lib/onyxcord/voice/network.rb +5 -357
  193. data/lib/onyxcord/webhooks/builder.rb +1 -1
  194. data/lib/onyxcord/webhooks/client.rb +13 -13
  195. data/lib/onyxcord/webhooks/modal/group_builder.rb +55 -0
  196. data/lib/onyxcord/webhooks/modal/label_builder.rb +183 -0
  197. data/lib/onyxcord/webhooks/modal.rb +2 -239
  198. data/lib/onyxcord/webhooks/version.rb +3 -3
  199. data/lib/onyxcord/webhooks/view/container_builder.rb +80 -0
  200. data/lib/onyxcord/webhooks/view/file_builder.rb +24 -0
  201. data/lib/onyxcord/webhooks/view/media_gallery_builder.rb +50 -0
  202. data/lib/onyxcord/webhooks/view/row_builder.rb +120 -0
  203. data/lib/onyxcord/webhooks/view/section_builder.rb +61 -0
  204. data/lib/onyxcord/webhooks/view/select_menu_builder.rb +77 -0
  205. data/lib/onyxcord/webhooks/view/separator_builder.rb +22 -0
  206. data/lib/onyxcord/webhooks/view/text_display_builder.rb +20 -0
  207. data/lib/onyxcord/webhooks/view.rb +9 -425
  208. data/lib/onyxcord.rb +106 -42
  209. metadata +207 -92
  210. data/.github/CONTRIBUTING.md +0 -13
  211. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  212. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
  213. data/.github/pull_request_template.md +0 -37
  214. data/.github/workflows/ci.yml +0 -78
  215. data/.github/workflows/codeql.yml +0 -65
  216. data/.github/workflows/deploy.yml +0 -54
  217. data/.github/workflows/release.yml +0 -51
  218. data/.gitignore +0 -17
  219. data/.markdownlint.json +0 -4
  220. data/.overcommit.yml +0 -7
  221. data/.rspec +0 -2
  222. data/.rubocop.yml +0 -129
  223. data/.yardopts +0 -1
  224. data/CHANGELOG.md +0 -257
  225. data/Gemfile +0 -6
  226. data/LICENSE.txt +0 -21
  227. data/README.md +0 -448
  228. data/Rakefile +0 -13
  229. data/lib/onyxcord/api/channel.rb +0 -703
  230. data/lib/onyxcord/api/server.rb +0 -775
  231. data/lib/onyxcord/async/runtime.rb +0 -30
  232. data/lib/onyxcord/commands/command_bot.rb +0 -505
  233. data/lib/onyxcord/data/interaction.rb +0 -1151
  234. data/lib/onyxcord/data.rb +0 -57
  235. data/lib/onyxcord/event_executor.rb +0 -153
  236. data/lib/onyxcord/gateway.rb +0 -505
  237. data/lib/onyxcord/http.rb +0 -186
  238. data/lib/onyxcord/json.rb +0 -49
  239. data/lib/onyxcord/rate_limiter/async_rest.rb +0 -150
  240. data/lib/onyxcord/rate_limiter/gateway.rb +0 -42
  241. data/lib/onyxcord/rate_limiter/rest.rb +0 -142
  242. data/lib/onyxcord/version.rb +0 -7
  243. data/lib/onyxcord/websocket.rb +0 -77
  244. data/onyxcord.gemspec +0 -63
  245. /data/lib/onyxcord/{configuration.rb → core/configuration.rb} +0 -0
  246. /data/lib/onyxcord/{errors.rb → core/errors.rb} +0 -0
  247. /data/lib/onyxcord/{upload.rb → internal/upload.rb} +0 -0
  248. /data/lib/onyxcord/{data → models}/attachment.rb +0 -0
  249. /data/lib/onyxcord/{data → models}/audit_logs.rb +0 -0
  250. /data/lib/onyxcord/{data → models}/call.rb +0 -0
  251. /data/lib/onyxcord/{data → models}/channel_tag.rb +0 -0
  252. /data/lib/onyxcord/{data → models}/component.rb +0 -0
  253. /data/lib/onyxcord/{data → models}/embed.rb +0 -0
  254. /data/lib/onyxcord/{data → models}/install_params.rb +0 -0
  255. /data/lib/onyxcord/{data → models}/integration.rb +0 -0
  256. /data/lib/onyxcord/{data → models}/message_activity.rb +0 -0
  257. /data/lib/onyxcord/{data → models}/overwrite.rb +0 -0
  258. /data/lib/onyxcord/{data → models}/reaction.rb +0 -0
  259. /data/lib/onyxcord/{data → models}/recipient.rb +0 -0
  260. /data/lib/onyxcord/{data → models}/role_connection_data.rb +0 -0
  261. /data/lib/onyxcord/{data → models}/role_subscription.rb +0 -0
  262. /data/lib/onyxcord/{data → models}/snapshot.rb +0 -0
  263. /data/lib/onyxcord/{data → models}/timestamp.rb +0 -0
  264. /data/lib/onyxcord/{data → models}/voice_region.rb +0 -0
  265. /data/lib/onyxcord/{data → models}/voice_state.rb +0 -0
  266. /data/lib/onyxcord/{colour_rgb.rb → utils/colour_rgb.rb} +0 -0
  267. /data/lib/onyxcord/{id_object.rb → utils/id_object.rb} +0 -0
  268. /data/lib/onyxcord/{message_components.rb → utils/message_components.rb} +0 -0
  269. /data/lib/onyxcord/{paginator.rb → utils/paginator.rb} +0 -0
  270. /data/lib/onyxcord/{permissions.rb → utils/permissions.rb} +0 -0
data/lib/onyxcord/http.rb DELETED
@@ -1,186 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'httpx'
4
- require 'net/http'
5
- require 'onyxcord/json'
6
- require 'onyxcord/upload'
7
- require 'securerandom'
8
- require 'uri'
9
-
10
- module OnyxCord
11
- # Modern HTTP adapter wrapping HTTPX with persistent connections, automatic
12
- # retries on 502, and a response interface compatible with the rest of OnyxCord.
13
- module HTTP
14
- # Lightweight response wrapper so call-sites that relied on RestClient's
15
- # `.body`, `.headers`, `.code` interface keep working unchanged.
16
- class Response
17
- # @return [String] the response body
18
- attr_reader :body
19
-
20
- # @return [Integer] the HTTP status code
21
- attr_reader :code
22
-
23
- # @return [Hash] the response headers (symbol keys, underscored)
24
- attr_reader :headers
25
-
26
- def initialize(httpx_response)
27
- @raw = httpx_response
28
- @body = httpx_response.body.to_s
29
- @code = httpx_response.respond_to?(:status) ? httpx_response.status : httpx_response.code.to_i
30
- response_headers = httpx_response.respond_to?(:headers) ? httpx_response.headers : httpx_response.to_hash
31
- @headers = normalize_headers(response_headers)
32
- end
33
-
34
- def to_s
35
- @body
36
- end
37
-
38
- # RestClient compatibility — some code calls `response` directly as a
39
- # string (implicit to_s).
40
- alias_method :to_str, :to_s
41
-
42
- private
43
-
44
- def normalize_headers(headers)
45
- headers.to_h.transform_keys do |key|
46
- key.to_s.tr('-', '_').downcase.to_sym
47
- end.transform_values do |value|
48
- value.is_a?(Array) && value.size == 1 ? value.first : value
49
- end
50
- end
51
- end
52
-
53
- module_function
54
-
55
- # The shared HTTPX session with persistent connections for the current thread.
56
- def session
57
- Thread.current[:onyxcord_http_session] ||= HTTPX.plugin(:persistent)
58
- .plugin(:follow_redirects)
59
- .with(
60
- fallback_protocol: 'http/1.1',
61
- ssl: { alpn_protocols: ['http/1.1'] }
62
- )
63
- end
64
-
65
- # Reset the HTTP session (useful for tests).
66
- def reset!
67
- Thread.current[:onyxcord_http_session] = nil
68
- end
69
-
70
- # Perform a raw HTTP request and return a {Response}.
71
- # @param type [Symbol] HTTP method (:get, :post, :put, :patch, :delete)
72
- # @param url [String] The full URL.
73
- # @param body [String, Hash, nil] The request body.
74
- # @param headers [Hash] Request headers.
75
- # @return [Response]
76
- def request(type, url, body = nil, **headers)
77
- http = session.with(headers: headers)
78
-
79
- raw = case type
80
- when :get
81
- http.get(url)
82
- when :post
83
- if multipart?(body)
84
- # Multipart upload
85
- post_multipart(url, body, headers)
86
- else
87
- http.post(url, body: body)
88
- end
89
- when :put
90
- http.put(url, body: body)
91
- when :patch
92
- http.patch(url, body: body)
93
- when :delete
94
- http.delete(url)
95
- else
96
- raise ArgumentError, "Unknown HTTP method: #{type}"
97
- end
98
-
99
- # HTTPX returns an error response object on network failures
100
- raise raw.error if raw.is_a?(HTTPX::ErrorResponse)
101
-
102
- Response.new(raw)
103
- end
104
-
105
- def post_multipart(url, body, headers)
106
- uri = URI(url)
107
- boundary = "----OnyxCord#{SecureRandom.hex(12)}"
108
- request = Net::HTTP::Post.new(uri)
109
- headers.each { |key, value| request[key.to_s] = value }
110
- request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
111
- request.body = multipart_body(body, boundary)
112
-
113
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
114
- http.request(request)
115
- end
116
- end
117
-
118
- def multipart_body(body, boundary)
119
- output = String.new.b
120
-
121
- multipart_parts(body).each do |part|
122
- key = part[:name]
123
- value = part[:value]
124
- output << "--#{boundary}\r\n"
125
- if part[:filename]
126
- value.rewind if value.respond_to?(:rewind)
127
- filename = part[:filename]
128
- output << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{filename}\"\r\n"
129
- output << "Content-Type: #{part[:content_type] || multipart_content_type(filename)}\r\n\r\n"
130
- output << value.read.to_s
131
- else
132
- output << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
133
- output << "\r\n"
134
- output << value.to_s
135
- end
136
- output << "\r\n"
137
- end
138
-
139
- output << "--#{boundary}--\r\n"
140
- end
141
-
142
- def multipart_content_type(filename)
143
- File.extname(filename).casecmp('.txt').zero? ? 'text/plain' : 'application/octet-stream'
144
- end
145
-
146
- def multipart?(body)
147
- return true if body.is_a?(Array) && body.all? { |part| part.is_a?(Hash) && part[:name] && part.key?(:value) }
148
-
149
- body.is_a?(Hash) && body.any? { |_, value| value.respond_to?(:read) || value.respond_to?(:path) }
150
- end
151
-
152
- def multipart_parts(body)
153
- return body if body.is_a?(Array)
154
-
155
- body.map do |key, value|
156
- if value.respond_to?(:read) || value.respond_to?(:path)
157
- upload = OnyxCord::Upload.wrap(value)
158
- { name: key, value: upload, filename: upload.filename, content_type: upload.content_type }
159
- else
160
- { name: key, value: value }
161
- end
162
- end
163
- end
164
-
165
- # Convenience wrappers
166
- def get(url, **headers)
167
- request(:get, url, nil, **headers)
168
- end
169
-
170
- def post(url, body = nil, **headers)
171
- request(:post, url, body, **headers)
172
- end
173
-
174
- def put(url, body = nil, **headers)
175
- request(:put, url, body, **headers)
176
- end
177
-
178
- def patch(url, body = nil, **headers)
179
- request(:patch, url, body, **headers)
180
- end
181
-
182
- def delete(url, **headers)
183
- request(:delete, url, nil, **headers)
184
- end
185
- end
186
- end
data/lib/onyxcord/json.rb DELETED
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'oj'
4
-
5
- # Configure Oj in compat mode so that all stdlib JSON.parse / .to_json calls
6
- # are transparently accelerated. This is loaded early by the main onyxcord.rb
7
- # entry point so every module benefits automatically.
8
- module OnyxCord
9
- # Fast Oj-backed JSON wrapper providing compatibility with stdlib JSON methods.
10
- module JSON
11
- Oj.default_options = { mode: :compat, symbol_keys: false }
12
-
13
- module_function
14
-
15
- # Fast JSON decode using Oj via stdlib JSON compatibility.
16
- # @param data [String] The JSON string to decode.
17
- # @return [Hash, Array, String, Numeric, nil]
18
- def decode(data, *args)
19
- ::JSON.parse(data, *args)
20
- end
21
-
22
- # Fast JSON encode using Oj via stdlib JSON compatibility.
23
- # @param data [Object] The object to encode as JSON.
24
- # @return [String]
25
- def encode(data, *args)
26
- ::JSON.generate(data, *args)
27
- end
28
-
29
- # Alias parse to decode for standard library compatibility
30
- def parse(data, *args)
31
- ::JSON.parse(data, *args)
32
- end
33
-
34
- # Alias generate to encode for standard library compatibility
35
- def generate(data, *args)
36
- ::JSON.generate(data, *args)
37
- end
38
-
39
- # Alias load to decode for Oj/JSON compatibility
40
- def load(data, *args)
41
- ::JSON.parse(data, *args)
42
- end
43
-
44
- # Alias dump to encode for Oj/JSON compatibility
45
- def dump(data, *args)
46
- ::JSON.generate(data, *args)
47
- end
48
- end
49
- end
@@ -1,150 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'onyxcord/async/runtime'
5
-
6
- module OnyxCord
7
- module RateLimiter
8
- class AsyncRest
9
- DEFAULT_ENTRY_TTL = 3600
10
- DEFAULT_PRUNE_INTERVAL = 100
11
-
12
- def initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL)
13
- @route_buckets = {}
14
- @bucket_locks = {}
15
- @bucket_last_used = {}
16
- @global_lock = Mutex.new
17
- @clock = clock
18
- @entry_ttl = entry_ttl
19
- @prune_interval = prune_interval
20
- @requests_since_prune = 0
21
- end
22
-
23
- def before_request(route, major_parameter)
24
- wait_for(@global_lock)
25
- wait_for(mutex_for(route, major_parameter))
26
- end
27
-
28
- def record_response(route, major_parameter, headers)
29
- headers = normalize_headers(headers)
30
- bucket = headers[:x_ratelimit_bucket]
31
- key = route_key(route, major_parameter)
32
- touch(key)
33
-
34
- if bucket
35
- bucket = bucket_key(bucket, major_parameter)
36
- @route_buckets[key] = bucket
37
- touch(bucket)
38
- end
39
-
40
- return unless headers[:x_ratelimit_remaining] == '0'
41
-
42
- wait_seconds = headers[:x_ratelimit_reset_after].to_f
43
- return unless wait_seconds.positive?
44
-
45
- async_wait(wait_seconds, mutex_for(route, major_parameter))
46
- end
47
-
48
- def handle_rate_limit(route, major_parameter, response)
49
- headers = normalize_headers(response.headers)
50
- wait_seconds = retry_after(response, headers)
51
-
52
- return unless wait_seconds.positive?
53
-
54
- if headers[:x_ratelimit_global] == 'true' || headers[:x_ratelimit_scope] == 'global'
55
- global_wait(wait_seconds)
56
- else
57
- async_wait(wait_seconds, mutex_for(route, major_parameter))
58
- end
59
- end
60
-
61
- def stats
62
- {
63
- route_buckets: @route_buckets.size,
64
- bucket_locks: @bucket_locks.size,
65
- tracked_keys: @bucket_last_used.size
66
- }
67
- end
68
-
69
- def prune!
70
- return 0 unless @entry_ttl
71
-
72
- cutoff = @clock.call - @entry_ttl
73
- stale_keys = @bucket_last_used.select { |_, last_used| last_used < cutoff }.keys
74
-
75
- stale_keys.each do |key|
76
- @bucket_locks.delete(key)
77
- @bucket_last_used.delete(key)
78
- @route_buckets.delete(key)
79
- @route_buckets.delete_if { |_, bucket_key| bucket_key == key }
80
- end
81
-
82
- @requests_since_prune = 0
83
- stale_keys.length
84
- end
85
-
86
- private
87
-
88
- def mutex_for(route, major_parameter)
89
- key = resolved_key(route, major_parameter)
90
- touch(key)
91
- @bucket_locks[key] ||= Mutex.new
92
- end
93
-
94
- def resolved_key(route, major_parameter)
95
- @route_buckets[route_key(route, major_parameter)] || route_key(route, major_parameter)
96
- end
97
-
98
- def route_key(route, major_parameter)
99
- [route, major_parameter].freeze
100
- end
101
-
102
- def bucket_key(bucket, major_parameter)
103
- [:bucket, bucket, major_parameter].freeze
104
- end
105
-
106
- def retry_after(response, headers)
107
- body = response.respond_to?(:body) ? response.body : response.to_s
108
- if body && !body.empty?
109
- data = JSON.parse(body)
110
- return data['retry_after'].to_f if data['retry_after']
111
- end
112
-
113
- (headers[:retry_after] || 0).to_f
114
- rescue JSON::ParserError
115
- (headers[:retry_after] || 0).to_f
116
- end
117
-
118
- def normalize_headers(headers)
119
- headers.each_with_object({}) do |(key, value), memo|
120
- memo[key.to_s.tr('-', '_').downcase.to_sym] = value.to_s
121
- end
122
- end
123
-
124
- def touch(key)
125
- @bucket_last_used[key] = @clock.call
126
- prune_if_needed
127
- end
128
-
129
- def prune_if_needed
130
- return unless @prune_interval
131
-
132
- @requests_since_prune += 1
133
- prune! if @requests_since_prune >= @prune_interval
134
- end
135
-
136
- def wait_for(mutex)
137
- mutex.lock
138
- mutex.unlock
139
- end
140
-
141
- def async_wait(time, mutex)
142
- mutex.synchronize { OnyxCord::AsyncRuntime.sleep(time) }
143
- end
144
-
145
- def global_wait(time)
146
- async_wait(time, @global_lock)
147
- end
148
- end
149
- end
150
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OnyxCord
4
- module RateLimiter
5
- # Sliding-window limiter for Gateway sends.
6
- class Gateway
7
- DEFAULT_LIMIT = 120
8
- DEFAULT_INTERVAL = 60
9
-
10
- def initialize(limit: DEFAULT_LIMIT, interval: DEFAULT_INTERVAL, clock: -> { Time.now }, sleeper: ->(duration) { sleep(duration) })
11
- @limit = limit
12
- @interval = interval
13
- @clock = clock
14
- @sleeper = sleeper
15
- @sent_at = []
16
- @mutex = Mutex.new
17
- end
18
-
19
- def wait
20
- @mutex.synchronize do
21
- now = @clock.call
22
- prune(now)
23
-
24
- if @sent_at.length >= @limit
25
- sleep_for = @interval - (now - @sent_at.first)
26
- @sleeper.call(sleep_for) if sleep_for.positive?
27
- now = @clock.call
28
- prune(now)
29
- end
30
-
31
- @sent_at << now
32
- end
33
- end
34
-
35
- private
36
-
37
- def prune(now)
38
- @sent_at.shift while @sent_at.any? && (now - @sent_at.first) >= @interval
39
- end
40
- end
41
- end
42
- end
@@ -1,142 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'onyxcord/json'
4
-
5
- module OnyxCord
6
- module RateLimiter
7
- # Discord REST rate limiter keyed by route/major parameter and remapped to
8
- # X-RateLimit-Bucket whenever Discord returns a concrete bucket id.
9
- class Rest
10
- DEFAULT_ENTRY_TTL = 3600
11
- DEFAULT_PRUNE_INTERVAL = 100
12
-
13
- def initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL)
14
- @route_buckets = {}
15
- @bucket_mutexes = {}
16
- @bucket_last_used = {}
17
- @global_mutex = Mutex.new
18
- @clock = clock
19
- @entry_ttl = entry_ttl
20
- @prune_interval = prune_interval
21
- @requests_since_prune = 0
22
- end
23
-
24
- def before_request(route, major_parameter)
25
- mutex_wait(mutex_for(route, major_parameter))
26
- mutex_wait(@global_mutex) if @global_mutex.locked?
27
- end
28
-
29
- def record_response(route, major_parameter, headers)
30
- headers = normalize_headers(headers)
31
- bucket = headers[:x_ratelimit_bucket]
32
- key = route_key(route, major_parameter)
33
- touch(key)
34
-
35
- if bucket
36
- bucket = bucket_key(bucket, major_parameter)
37
- @route_buckets[key] = bucket
38
- touch(bucket)
39
- end
40
-
41
- return unless headers[:x_ratelimit_remaining] == '0'
42
-
43
- wait_seconds = headers[:x_ratelimit_reset_after].to_f
44
- return unless wait_seconds.positive?
45
-
46
- sync_wait(wait_seconds, mutex_for(route, major_parameter))
47
- end
48
-
49
- def handle_rate_limit(route, major_parameter, response)
50
- headers = normalize_headers(response.headers)
51
- mutex = headers[:x_ratelimit_global] == 'true' || headers[:x_ratelimit_scope] == 'global' ? @global_mutex : mutex_for(route, major_parameter)
52
- wait_seconds = retry_after(response, headers)
53
-
54
- sync_wait(wait_seconds, mutex) if wait_seconds.positive?
55
- end
56
-
57
- def stats
58
- {
59
- route_buckets: @route_buckets.size,
60
- bucket_mutexes: @bucket_mutexes.size,
61
- tracked_keys: @bucket_last_used.size
62
- }
63
- end
64
-
65
- def prune!
66
- return 0 unless @entry_ttl
67
-
68
- cutoff = @clock.call - @entry_ttl
69
- stale_keys = @bucket_last_used.select { |_, last_used| last_used < cutoff }.keys
70
-
71
- stale_keys.each do |key|
72
- @bucket_mutexes.delete(key)
73
- @bucket_last_used.delete(key)
74
- @route_buckets.delete(key)
75
- @route_buckets.delete_if { |_, bucket_key| bucket_key == key }
76
- end
77
-
78
- @requests_since_prune = 0
79
- stale_keys.length
80
- end
81
-
82
- private
83
-
84
- def mutex_for(route, major_parameter)
85
- key = resolved_key(route, major_parameter)
86
- touch(key)
87
- @bucket_mutexes[key] ||= Mutex.new
88
- end
89
-
90
- def resolved_key(route, major_parameter)
91
- @route_buckets[route_key(route, major_parameter)] || route_key(route, major_parameter)
92
- end
93
-
94
- def route_key(route, major_parameter)
95
- [route, major_parameter].freeze
96
- end
97
-
98
- def bucket_key(bucket, major_parameter)
99
- [:bucket, bucket, major_parameter].freeze
100
- end
101
-
102
- def retry_after(response, headers)
103
- body = response.respond_to?(:body) ? response.body : response.to_s
104
- if body && !body.empty?
105
- data = JSON.parse(body)
106
- return data['retry_after'].to_f if data['retry_after']
107
- end
108
-
109
- (headers[:retry_after] || 0).to_f
110
- rescue JSON::ParserError
111
- (headers[:retry_after] || 0).to_f
112
- end
113
-
114
- def normalize_headers(headers)
115
- headers.each_with_object({}) do |(key, value), memo|
116
- memo[key.to_s.tr('-', '_').downcase.to_sym] = value.to_s
117
- end
118
- end
119
-
120
- def touch(key)
121
- @bucket_last_used[key] = @clock.call
122
- prune_if_needed
123
- end
124
-
125
- def prune_if_needed
126
- return unless @prune_interval
127
-
128
- @requests_since_prune += 1
129
- prune! if @requests_since_prune >= @prune_interval
130
- end
131
-
132
- def sync_wait(time, mutex)
133
- mutex.synchronize { sleep time }
134
- end
135
-
136
- def mutex_wait(mutex)
137
- mutex.lock
138
- mutex.unlock
139
- end
140
- end
141
- end
142
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # OnyxCord and all its functionality, in this case only the version.
4
- module OnyxCord
5
- # The current version of onyxcord.
6
- VERSION = '2.1.0'
7
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'onyxcord/async/runtime'
4
- require 'async/http/endpoint'
5
- require 'async/http/protocol/http11'
6
- require 'async/websocket/client'
7
-
8
- module OnyxCord
9
- class WebSocket
10
- attr_reader :connected
11
-
12
- alias_method :connected?, :connected
13
-
14
- def initialize(host, open_handler, message_handler, error_handler, close_handler)
15
- @host = host
16
- @open_handler = open_handler
17
- @message_handler = message_handler
18
- @error_handler = error_handler
19
- @close_handler = close_handler
20
-
21
- @connection = nil
22
- @connected = false
23
-
24
- connect
25
- end
26
-
27
- def send(data)
28
- return unless @connection
29
-
30
- data = data.to_json if data.is_a?(Hash)
31
- @connection.write(Protocol::WebSocket::TextMessage.generate(data))
32
- @connection.flush
33
- rescue StandardError => e
34
- @error_handler&.call(e)
35
- end
36
-
37
- def close
38
- @connected = false
39
- @connection&.close
40
- rescue StandardError
41
- # Ignore errors on close
42
- end
43
-
44
- private
45
-
46
- def connect
47
- endpoint = websocket_endpoint(@host)
48
-
49
- @task = OnyxCord::AsyncRuntime.async do
50
- Async::WebSocket::Client.connect(endpoint, extensions: nil) do |connection|
51
- @connection = connection
52
- @connected = true
53
- @open_handler&.call
54
-
55
- while (message = connection.read)
56
- @message_handler&.call(message.to_str)
57
- end
58
- rescue StandardError => e
59
- @error_handler&.call(e)
60
- ensure
61
- @connected = false
62
- @close_handler&.call(nil)
63
- end
64
- end
65
- rescue StandardError => e
66
- @error_handler&.call(e)
67
- end
68
-
69
- def websocket_endpoint(url)
70
- Async::HTTP::Endpoint.parse(
71
- url,
72
- protocol: Async::HTTP::Protocol::HTTP11,
73
- alpn_protocols: ['http/1.1']
74
- )
75
- end
76
- end
77
- end