onyxcord 2.0.13 → 3.2.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 (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/{errors.rb → core/errors.rb} +18 -3
  27. data/lib/onyxcord/{logger.rb → core/logger.rb} +5 -0
  28. data/lib/onyxcord/core/version.rb +5 -0
  29. data/lib/onyxcord/events/await/base.rb +48 -0
  30. data/lib/onyxcord/events/await.rb +1 -46
  31. data/lib/onyxcord/events/ban/base.rb +54 -0
  32. data/lib/onyxcord/events/ban/remove.rb +11 -0
  33. data/lib/onyxcord/events/bans.rb +2 -58
  34. data/lib/onyxcord/events/channels/base.rb +91 -0
  35. data/lib/onyxcord/events/channels/delete.rb +73 -0
  36. data/lib/onyxcord/events/channels/pins.rb +44 -0
  37. data/lib/onyxcord/events/channels/recipients.rb +77 -0
  38. data/lib/onyxcord/events/channels.rb +4 -239
  39. data/lib/onyxcord/events/guilds/audit_logs.rb +79 -0
  40. data/lib/onyxcord/events/guilds/base.rb +97 -0
  41. data/lib/onyxcord/events/guilds/emoji.rb +147 -0
  42. data/lib/onyxcord/events/guilds.rb +3 -267
  43. data/lib/onyxcord/events/handlers/channels.rb +133 -0
  44. data/lib/onyxcord/events/handlers/core.rb +146 -0
  45. data/lib/onyxcord/events/handlers/guilds.rb +375 -0
  46. data/lib/onyxcord/events/handlers/interactions.rb +238 -0
  47. data/lib/onyxcord/events/handlers/lifetime.rb +71 -0
  48. data/lib/onyxcord/events/handlers/messages.rb +218 -0
  49. data/lib/onyxcord/events/handlers/presence.rb +56 -0
  50. data/lib/onyxcord/events/handlers/raw.rb +39 -0
  51. data/lib/onyxcord/events/handlers/reactions.rb +98 -0
  52. data/lib/onyxcord/events/handlers/scheduled_events.rb +243 -0
  53. data/lib/onyxcord/events/handlers/voice.rb +46 -0
  54. data/lib/onyxcord/events/integration/base.rb +45 -0
  55. data/lib/onyxcord/events/integration/create.rb +11 -0
  56. data/lib/onyxcord/events/integration/delete.rb +48 -0
  57. data/lib/onyxcord/events/integration/update.rb +11 -0
  58. data/lib/onyxcord/events/integrations.rb +4 -98
  59. data/lib/onyxcord/events/interactions/application_commands.rb +185 -0
  60. data/lib/onyxcord/events/interactions/autocomplete.rb +62 -0
  61. data/lib/onyxcord/events/interactions/base.rb +175 -0
  62. data/lib/onyxcord/events/interactions/components.rb +221 -0
  63. data/lib/onyxcord/events/interactions/permissions.rb +53 -0
  64. data/lib/onyxcord/events/interactions.rb +5 -622
  65. data/lib/onyxcord/events/invite/create.rb +79 -0
  66. data/lib/onyxcord/events/invite/delete.rb +55 -0
  67. data/lib/onyxcord/events/invites.rb +2 -125
  68. data/lib/onyxcord/events/lifetime/base.rb +31 -0
  69. data/lib/onyxcord/events/lifetime.rb +1 -29
  70. data/lib/onyxcord/events/member/add.rb +12 -0
  71. data/lib/onyxcord/events/member/base.rb +67 -0
  72. data/lib/onyxcord/events/member/delete.rb +21 -0
  73. data/lib/onyxcord/events/member/update.rb +25 -0
  74. data/lib/onyxcord/events/members.rb +4 -108
  75. data/lib/onyxcord/events/message/base.rb +295 -0
  76. data/lib/onyxcord/events/message/ids.rb +71 -0
  77. data/lib/onyxcord/events/message/mentions.rb +55 -0
  78. data/lib/onyxcord/events/message/updates.rb +27 -0
  79. data/lib/onyxcord/events/message.rb +4 -397
  80. data/lib/onyxcord/events/poll/add.rb +11 -0
  81. data/lib/onyxcord/events/poll/base.rb +106 -0
  82. data/lib/onyxcord/events/poll/remove.rb +11 -0
  83. data/lib/onyxcord/events/polls.rb +3 -116
  84. data/lib/onyxcord/events/presence/playing.rb +77 -0
  85. data/lib/onyxcord/events/presence/status.rb +60 -0
  86. data/lib/onyxcord/events/presence.rb +2 -129
  87. data/lib/onyxcord/events/raw/base.rb +74 -0
  88. data/lib/onyxcord/events/raw.rb +1 -72
  89. data/lib/onyxcord/events/reactions/base.rb +143 -0
  90. data/lib/onyxcord/events/reactions/clear.rb +99 -0
  91. data/lib/onyxcord/events/reactions.rb +2 -216
  92. data/lib/onyxcord/events/role/create.rb +46 -0
  93. data/lib/onyxcord/events/role/delete.rb +40 -0
  94. data/lib/onyxcord/events/role/update.rb +11 -0
  95. data/lib/onyxcord/events/roles.rb +3 -85
  96. data/lib/onyxcord/events/scheduled_events/events.rb +112 -0
  97. data/lib/onyxcord/events/scheduled_events/users.rb +91 -0
  98. data/lib/onyxcord/events/scheduled_events.rb +2 -169
  99. data/lib/onyxcord/events/thread/base.rb +49 -0
  100. data/lib/onyxcord/events/thread/members.rb +44 -0
  101. data/lib/onyxcord/events/thread/update.rb +18 -0
  102. data/lib/onyxcord/events/threads.rb +3 -98
  103. data/lib/onyxcord/events/typing/base.rb +73 -0
  104. data/lib/onyxcord/events/typing.rb +1 -71
  105. data/lib/onyxcord/events/voice/server_update.rb +48 -0
  106. data/lib/onyxcord/events/voice/state_update.rb +137 -0
  107. data/lib/onyxcord/events/voice_server_update.rb +2 -46
  108. data/lib/onyxcord/events/voice_state_update.rb +2 -135
  109. data/lib/onyxcord/events/webhook/update.rb +65 -0
  110. data/lib/onyxcord/events/webhooks.rb +1 -63
  111. data/lib/onyxcord/gateway/client.rb +460 -0
  112. data/lib/onyxcord/interactions/command.rb +120 -0
  113. data/lib/onyxcord/interactions/context.rb +109 -0
  114. data/lib/onyxcord/interactions/internal/application_command.rb +203 -0
  115. data/lib/onyxcord/interactions/internal/message.rb +152 -0
  116. data/lib/onyxcord/interactions/internal/metadata.rb +109 -0
  117. data/lib/onyxcord/interactions/internal/option_builder.rb +230 -0
  118. data/lib/onyxcord/interactions/internal/permission_builder.rb +90 -0
  119. data/lib/onyxcord/interactions/option.rb +88 -0
  120. data/lib/onyxcord/interactions/registry.rb +50 -0
  121. data/lib/onyxcord/interactions.rb +16 -0
  122. data/lib/onyxcord/internal/async_runtime.rb +32 -0
  123. data/lib/onyxcord/internal/event_bus.rb +591 -0
  124. data/lib/onyxcord/internal/event_executor.rb +155 -0
  125. data/lib/onyxcord/internal/gateway/opcodes.rb +22 -0
  126. data/lib/onyxcord/internal/gateway/session.rb +45 -0
  127. data/lib/onyxcord/internal/http.rb +189 -0
  128. data/lib/onyxcord/internal/json.rb +51 -0
  129. data/lib/onyxcord/internal/message_payload.rb +78 -0
  130. data/lib/onyxcord/internal/rate_limiter/async_rest.rb +151 -0
  131. data/lib/onyxcord/internal/rate_limiter/gateway.rb +44 -0
  132. data/lib/onyxcord/internal/rate_limiter/rest.rb +144 -0
  133. data/lib/onyxcord/internal/upload.rb +39 -0
  134. data/lib/onyxcord/internal/websocket.rb +79 -0
  135. data/lib/onyxcord/light/data.rb +1 -1
  136. data/lib/onyxcord/light/integrations.rb +1 -1
  137. data/lib/onyxcord/light/light_bot.rb +7 -7
  138. data/lib/onyxcord/{data → models}/activity.rb +2 -2
  139. data/lib/onyxcord/{data → models}/application.rb +3 -3
  140. data/lib/onyxcord/{data → models}/avatar_decoration.rb +1 -1
  141. data/lib/onyxcord/{data → models}/channel.rb +23 -23
  142. data/lib/onyxcord/{data → models}/collectibles.rb +1 -1
  143. data/lib/onyxcord/{data → models}/emoji.rb +1 -1
  144. data/lib/onyxcord/models/interaction.rb +450 -0
  145. data/lib/onyxcord/{data → models}/invite.rb +1 -1
  146. data/lib/onyxcord/{data → models}/member.rb +11 -11
  147. data/lib/onyxcord/{data → models}/message.rb +22 -14
  148. data/lib/onyxcord/{data → models}/poll.rb +2 -2
  149. data/lib/onyxcord/{data → models}/primary_server.rb +1 -1
  150. data/lib/onyxcord/{data → models}/profile.rb +4 -4
  151. data/lib/onyxcord/{data → models}/role.rb +6 -12
  152. data/lib/onyxcord/{data → models}/scheduled_event.rb +5 -5
  153. data/lib/onyxcord/{data → models}/server.rb +41 -41
  154. data/lib/onyxcord/{data → models}/server_preview.rb +2 -2
  155. data/lib/onyxcord/{data → models}/team.rb +1 -1
  156. data/lib/onyxcord/{data → models}/user.rb +5 -5
  157. data/lib/onyxcord/{data → models}/webhook.rb +13 -11
  158. data/lib/onyxcord/models.rb +57 -0
  159. data/lib/onyxcord/{api.rb → rest/client.rb} +83 -27
  160. data/lib/onyxcord/{api → rest/routes}/application.rb +79 -57
  161. data/lib/onyxcord/rest/routes/channel/base.rb +73 -0
  162. data/lib/onyxcord/rest/routes/channel/groups.rb +78 -0
  163. data/lib/onyxcord/rest/routes/channel/messages.rb +130 -0
  164. data/lib/onyxcord/rest/routes/channel/permissions.rb +73 -0
  165. data/lib/onyxcord/rest/routes/channel/pins.rb +45 -0
  166. data/lib/onyxcord/rest/routes/channel/polls.rb +32 -0
  167. data/lib/onyxcord/rest/routes/channel/reactions.rb +86 -0
  168. data/lib/onyxcord/rest/routes/channel/threads.rb +176 -0
  169. data/lib/onyxcord/rest/routes/channel/webhooks.rb +47 -0
  170. data/lib/onyxcord/rest/routes/channel.rb +12 -0
  171. data/lib/onyxcord/{api/interaction.rb → rest/routes/interaction/base.rb} +10 -33
  172. data/lib/onyxcord/rest/routes/interaction/response.rb +24 -0
  173. data/lib/onyxcord/rest/routes/interaction.rb +4 -0
  174. data/lib/onyxcord/{api → rest/routes}/invite.rb +7 -7
  175. data/lib/onyxcord/rest/routes/server/admin.rb +174 -0
  176. data/lib/onyxcord/rest/routes/server/assets.rb +25 -0
  177. data/lib/onyxcord/rest/routes/server/bans.rb +67 -0
  178. data/lib/onyxcord/rest/routes/server/base.rb +77 -0
  179. data/lib/onyxcord/rest/routes/server/channels.rb +58 -0
  180. data/lib/onyxcord/rest/routes/server/emoji.rb +48 -0
  181. data/lib/onyxcord/rest/routes/server/members.rb +109 -0
  182. data/lib/onyxcord/rest/routes/server/messages.rb +19 -0
  183. data/lib/onyxcord/rest/routes/server/roles.rb +156 -0
  184. data/lib/onyxcord/rest/routes/server/scheduled_events.rb +86 -0
  185. data/lib/onyxcord/rest/routes/server.rb +12 -0
  186. data/lib/onyxcord/{api → rest/routes}/user.rb +23 -23
  187. data/lib/onyxcord/{api → rest/routes}/webhook.rb +30 -32
  188. data/lib/onyxcord/{allowed_mentions.rb → utils/allowed_mentions.rb} +9 -1
  189. data/lib/onyxcord/utils/timestamp.rb +69 -0
  190. data/lib/onyxcord/voice/{voice_bot.rb → client.rb} +4 -4
  191. data/lib/onyxcord/voice/encoder.rb +1 -1
  192. data/lib/onyxcord/voice/network/udp.rb +135 -0
  193. data/lib/onyxcord/voice/network/websocket.rb +225 -0
  194. data/lib/onyxcord/voice/network.rb +5 -357
  195. data/lib/onyxcord/webhooks/builder.rb +1 -1
  196. data/lib/onyxcord/webhooks/client.rb +13 -13
  197. data/lib/onyxcord/webhooks/modal/group_builder.rb +55 -0
  198. data/lib/onyxcord/webhooks/modal/label_builder.rb +183 -0
  199. data/lib/onyxcord/webhooks/modal.rb +2 -239
  200. data/lib/onyxcord/webhooks/version.rb +3 -3
  201. data/lib/onyxcord/webhooks/view/container_builder.rb +80 -0
  202. data/lib/onyxcord/webhooks/view/file_builder.rb +24 -0
  203. data/lib/onyxcord/webhooks/view/media_gallery_builder.rb +50 -0
  204. data/lib/onyxcord/webhooks/view/row_builder.rb +120 -0
  205. data/lib/onyxcord/webhooks/view/section_builder.rb +61 -0
  206. data/lib/onyxcord/webhooks/view/select_menu_builder.rb +77 -0
  207. data/lib/onyxcord/webhooks/view/separator_builder.rb +22 -0
  208. data/lib/onyxcord/webhooks/view/text_display_builder.rb +20 -0
  209. data/lib/onyxcord/webhooks/view.rb +9 -425
  210. data/lib/onyxcord.rb +106 -40
  211. metadata +208 -91
  212. data/.github/CONTRIBUTING.md +0 -13
  213. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  214. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
  215. data/.github/pull_request_template.md +0 -37
  216. data/.github/workflows/ci.yml +0 -78
  217. data/.github/workflows/codeql.yml +0 -65
  218. data/.github/workflows/deploy.yml +0 -54
  219. data/.github/workflows/release.yml +0 -51
  220. data/.gitignore +0 -17
  221. data/.markdownlint.json +0 -4
  222. data/.overcommit.yml +0 -7
  223. data/.rspec +0 -2
  224. data/.rubocop.yml +0 -129
  225. data/.yardopts +0 -1
  226. data/CHANGELOG.md +0 -202
  227. data/Gemfile +0 -6
  228. data/LICENSE.txt +0 -21
  229. data/README.md +0 -448
  230. data/Rakefile +0 -13
  231. data/lib/onyxcord/api/channel.rb +0 -705
  232. data/lib/onyxcord/api/server.rb +0 -775
  233. data/lib/onyxcord/async/runtime.rb +0 -30
  234. data/lib/onyxcord/commands/command_bot.rb +0 -505
  235. data/lib/onyxcord/data/interaction.rb +0 -1143
  236. data/lib/onyxcord/data.rb +0 -57
  237. data/lib/onyxcord/event_executor.rb +0 -153
  238. data/lib/onyxcord/gateway.rb +0 -500
  239. data/lib/onyxcord/http.rb +0 -115
  240. data/lib/onyxcord/json.rb +0 -49
  241. data/lib/onyxcord/rate_limiter/async_rest.rb +0 -149
  242. data/lib/onyxcord/rate_limiter/gateway.rb +0 -42
  243. data/lib/onyxcord/rate_limiter/rest.rb +0 -142
  244. data/lib/onyxcord/version.rb +0 -7
  245. data/lib/onyxcord/websocket.rb +0 -77
  246. data/onyxcord.gemspec +0 -63
  247. /data/lib/onyxcord/{configuration.rb → core/configuration.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/bot.rb CHANGED
@@ -1,50 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'onyxcord/http'
4
3
  require 'zlib'
5
-
6
- require 'onyxcord/configuration'
7
- require 'onyxcord/event_executor'
8
- require 'onyxcord/events/message'
9
- require 'onyxcord/events/typing'
10
- require 'onyxcord/events/lifetime'
11
- require 'onyxcord/events/presence'
12
- require 'onyxcord/events/voice_state_update'
13
- require 'onyxcord/events/channels'
14
- require 'onyxcord/events/members'
15
- require 'onyxcord/events/roles'
16
- require 'onyxcord/events/guilds'
17
- require 'onyxcord/events/await'
18
- require 'onyxcord/events/bans'
19
- require 'onyxcord/events/raw'
20
- require 'onyxcord/events/reactions'
21
- require 'onyxcord/events/webhooks'
22
- require 'onyxcord/events/invites'
23
- require 'onyxcord/events/interactions'
24
- require 'onyxcord/events/threads'
25
- require 'onyxcord/events/integrations'
26
- require 'onyxcord/events/scheduled_events'
27
- require 'onyxcord/events/polls'
28
-
29
- require 'onyxcord/api'
30
- require 'onyxcord/api/channel'
31
- require 'onyxcord/api/server'
32
- require 'onyxcord/api/invite'
33
- require 'onyxcord/api/interaction'
34
- require 'onyxcord/api/application'
35
-
36
- require 'onyxcord/async/runtime'
37
- require 'onyxcord/errors'
38
- require 'onyxcord/message_components'
39
- require 'onyxcord/data'
40
- require 'onyxcord/await'
41
- require 'onyxcord/container'
42
- require 'onyxcord/websocket'
43
- require 'onyxcord/cache'
44
- require 'onyxcord/gateway'
45
-
46
- require 'onyxcord/application_commands'
47
- require 'onyxcord/voice/voice_bot'
4
+ require_relative 'core/bot/runtime'
5
+ require_relative 'core/bot/invites'
6
+ require_relative 'core/bot/voice'
7
+ require_relative 'core/bot/messaging'
8
+ require_relative 'core/bot/oauth'
9
+ require_relative 'core/bot/presence'
10
+ require_relative 'core/bot/awaits'
11
+ require_relative 'core/bot/application_commands'
48
12
 
49
13
  module OnyxCord
50
14
  # Represents a Discord bot, including servers, users, etc.
@@ -81,11 +45,20 @@ module OnyxCord
81
45
  # @return [Hash] the normalized cache policy for this bot.
82
46
  attr_reader :cache_policy
83
47
 
84
- # @return [OnyxCord::EventExecutor::Inline, OnyxCord::EventExecutor::Pool]
48
+ # @return [Internal::EventExecutor::Inline, Internal::EventExecutor::Pool, Internal::EventExecutor::AsyncPool]
85
49
  attr_reader :event_executor
86
50
 
87
51
  include EventContainer
88
52
  include Cache
53
+ include Internal::EventBus
54
+ include Runtime
55
+ include Invites
56
+ include VoiceControl
57
+ include Messaging
58
+ include OAuth
59
+ include Presence
60
+ include Awaits
61
+ include ApplicationCommands
89
62
 
90
63
  # Makes a new bot with the given authentication data. It will be ready to be added event handlers to and can
91
64
  # eventually be run with {#run}.
@@ -176,7 +149,7 @@ module OnyxCord
176
149
  end
177
150
 
178
151
  @token = process_token(@type, token)
179
- @gateway = Gateway.new(self, @token, @shard_key, @compress_mode, @intents)
152
+ @gateway = Gateway::Client.new(self, @token, @shard_key, @compress_mode, @intents)
180
153
 
181
154
  init_cache
182
155
 
@@ -188,7 +161,7 @@ module OnyxCord
188
161
 
189
162
  @current_thread = 0
190
163
  @current_thread_mutex = Mutex.new
191
- @event_executor = EventExecutor.build(executor_type, workers: executor_workers, queue_size: executor_queue_size)
164
+ @event_executor = Internal::EventExecutor.build(executor_type, workers: executor_workers, queue_size: executor_queue_size)
192
165
  @event_threads = @event_executor.threads
193
166
 
194
167
  @status = :online
@@ -261,7 +234,7 @@ module OnyxCord
261
234
  def profile
262
235
  return @profile if @profile
263
236
 
264
- response = OnyxCord::API::User.profile(@token)
237
+ response = OnyxCord::REST::User.profile(@token)
265
238
  @profile = Profile.new(JSON.parse(response), self)
266
239
  end
267
240
 
@@ -270,7 +243,7 @@ module OnyxCord
270
243
  # The bot's OAuth application.
271
244
  # @return [Application] The bot's application info.
272
245
  def bot_application
273
- response = API.oauth_application(token)
246
+ response = REST.oauth_application(token)
274
247
  Application.new(JSON.parse(response), self)
275
248
  end
276
249
 
@@ -280,7 +253,7 @@ module OnyxCord
280
253
  # Get the role connection metadata records associated with this application.
281
254
  # @return [Array<RoleConnectionMetadata>] the role connection metadata records associated with this application.
282
255
  def role_connection_metadata_records
283
- response = API::Application.get_application_role_connection_metadata_records(@bot.token, @id)
256
+ response = REST::Application.get_application_role_connection_metadata_records(@bot.token, @id)
284
257
  JSON.parse(response).map { |role_connection| RoleConnectionMetadata.new(role_connection, @bot) }
285
258
  end
286
259
 
@@ -288,7 +261,7 @@ module OnyxCord
288
261
  # {API} methods.
289
262
  # @return [String] The API token.
290
263
  def token
291
- API.bot_name = @name
264
+ REST.bot_name = @name
292
265
  @token
293
266
  end
294
267
 
@@ -298,1717 +271,9 @@ module OnyxCord
298
271
  @token.split(' ').last
299
272
  end
300
273
 
301
- # Runs the bot, which logs into Discord and connects the WebSocket. This
302
- # prevents all further execution unless it is executed with
303
- # `background` = `true`.
304
- # @param background [true, false] If it is `true`, then the bot will run in
305
- # another thread to allow further execution. If it is `false`, this method
306
- # will block until {#stop} is called. If the bot is run with `true`, make
307
- # sure to eventually call {#join} so the script doesn't stop prematurely.
308
- # @note Running the bot in the background means that you can call some
309
- # methods that require a gateway connection *before* that connection is
310
- # established. In most cases an exception will be raised if you try to do
311
- # this. If you need a way to safely run code after the bot is fully
312
- # connected, use a {#ready} event handler instead.
313
- def run(background = false)
314
- if background
315
- @run_task = OnyxCord::AsyncRuntime.async { run_forever }
316
- return @run_task
317
- end
318
-
319
- OnyxCord::AsyncRuntime.run { run_forever }
320
- end
321
-
322
- def run_forever
323
- @gateway.run
324
- end
325
-
326
- def join
327
- @run_task&.wait
328
- end
329
- alias_method :sync, :join
330
-
331
- def stop(_no_sync = nil)
332
- @gateway.stop
333
- @event_executor.shutdown
334
- @run_task&.stop if @run_task.respond_to?(:stop)
335
- nil
336
- end
337
-
338
- # @return [true, false] whether or not the bot is currently connected to Discord.
339
- def connected?
340
- @gateway.open?
341
- end
342
-
343
- # Makes the bot join an invite to a server.
344
- # @param invite [String, Invite] The invite to join. For possible formats see {#resolve_invite_code}.
345
- def accept_invite(invite)
346
- resolved = invite(invite).code
347
- API::Invite.accept(token, resolved)
348
- end
349
-
350
- # Creates an OAuth invite URL that can be used to invite this bot to a particular server.
351
- # @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created.
352
- # @param permission_bits [String, Integer] Permission bits that should be appended to invite url.
353
- # @param redirect_uri [String] Redirect URI that should be appended to invite url.
354
- # @param scopes [Array<String>] Scopes that should be appended to invite url.
355
- # @return [String] the OAuth invite URL.
356
- def invite_url(server: nil, permission_bits: nil, redirect_uri: nil, scopes: ['bot'])
357
- @client_id ||= bot_application.id
358
-
359
- query = URI.encode_www_form({
360
- client_id: @client_id,
361
- guild_id: server&.id,
362
- permissions: permission_bits,
363
- redirect_uri: redirect_uri,
364
- scope: scopes.join(' ')
365
- }.compact)
366
-
367
- "https://discord.com/oauth2/authorize?#{query}"
368
- end
369
-
370
- # @return [Hash<Integer => VoiceBot>] the voice connections this bot currently has, by the server ID to which they are connected.
371
- attr_reader :voices
372
-
373
- # Gets the voice bot for a particular server or channel. You can connect to a new channel using the {#voice_connect}
374
- # method.
375
- # @param thing [Channel, Server, Integer] the server or channel you want to get the voice bot for, or its ID.
376
- # @return [Voice::VoiceBot, nil] the VoiceBot for the thing you specified, or nil if there is no connection yet
377
- def voice(thing)
378
- id = thing.resolve_id
379
- return @voices[id] if @voices[id]
380
-
381
- channel = channel(id)
382
- return nil unless channel
383
-
384
- server_id = channel.server.id
385
- return @voices[server_id] if @voices[server_id]
386
- end
387
-
388
- # Connects to a voice channel, initializes network connections and returns the {Voice::VoiceBot} over which audio
389
- # data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already
390
- # connected to voice, the existing connection will be terminated - you don't have to call
391
- # {OnyxCord::Voice::VoiceBot#destroy} before calling this method.
392
- # @param chan [Channel, String, Integer] The voice channel, or its ID, to connect to.
393
- # @param encrypted [true, false] Whether voice communication should be encrypted using
394
- # (uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)
395
- # @return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.
396
- def voice_connect(chan, encrypted = true)
397
- raise ArgumentError, 'Unencrypted voice connections are no longer supported.' unless encrypted
398
-
399
- chan = channel(chan.resolve_id)
400
- server_id = chan.server.id
401
-
402
- if @voices[chan.id]
403
- debug('Voice bot exists already! Destroying it')
404
- @voices[chan.id].destroy
405
- @voices.delete(chan.id)
406
- end
407
-
408
- debug("Got voice channel: #{chan}")
409
-
410
- @should_connect_to_voice[server_id] = chan
411
- @gateway.send_voice_state_update(server_id.to_s, chan.id.to_s, false, false)
412
-
413
- debug('Voice channel init packet sent! Now waiting.')
414
-
415
- OnyxCord::AsyncRuntime.sleep(0.05) until @voices[server_id]
416
- debug('Voice connect succeeded!')
417
- @voices[server_id]
418
- end
419
-
420
- # Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use
421
- # {OnyxCord::Voice::VoiceBot#destroy} rather than this.
422
- # @param server [Server, String, Integer] The server, or server ID, the voice connection is on.
423
- # @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you're calling this method
424
- # directly, you should leave it as true.
425
- def voice_destroy(server, destroy_vws = true)
426
- server = server.resolve_id
427
- @gateway.send_voice_state_update(server.to_s, nil, false, false)
428
- @voices[server].destroy if @voices[server] && destroy_vws
429
- @voices.delete(server)
430
- end
431
-
432
- # Revokes an invite to a server. Will fail unless you have the *Manage Server* permission.
433
- # It is recommended that you use {Invite#delete} instead.
434
- # @param code [String, Invite] The invite to revoke. For possible formats see {#resolve_invite_code}.
435
- def delete_invite(code)
436
- invite = resolve_invite_code(code)
437
- API::Invite.delete(token, invite)
438
- end
439
-
440
- # Sends a text message to a channel given its ID and the message's content.
441
- # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
442
- # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
443
- # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
444
- # @param embeds [Hash, OnyxCord::Webhooks::Embed, Array<Hash>, Array<OnyxCord::Webhooks::Embed> nil] The rich embed(s) to append to this message.
445
- # @param allowed_mentions [Hash, OnyxCord::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
446
- # @param message_reference [Message, String, Integer, Hash, nil] The message, or message ID, to reply to if any.
447
- # @param components [View, Array<Hash>] Interaction components to associate with this message.
448
- # @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2), SUPPRESS_NOTIFICATIONS (1 << 12), and IS_COMPONENTS_V2 (1 << 15) can be set.
449
- # @param nonce [String, nil] A optional nonce in order to verify that a message was sent. Maximum of twenty-five characters.
450
- # @param enforce_nonce [true, false] Whether the nonce should be enforced and used for message de-duplication.
451
- # @param poll [Hash, Poll::Builder, Poll, nil] The poll that should be attached to this message.
452
- # @return [Message] The message that was sent.
453
- def send_message(channel, content, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0, nonce = nil, enforce_nonce = false, poll = nil)
454
- channel = channel.resolve_id
455
- debug("Sending message to #{channel} with content '#{content}'")
456
- allowed_mentions = { parse: [] } if allowed_mentions == false
457
- message_reference = { message_id: message_reference.resolve_id } if message_reference.respond_to?(:resolve_id)
458
- embeds = (embeds.instance_of?(Array) ? embeds.map(&:to_hash) : [embeds&.to_hash]).compact
459
- flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
460
-
461
- response = API::Channel.create_message(token, channel, content, tts, embeds, nonce, attachments, allowed_mentions&.to_hash, message_reference, components, flags, enforce_nonce, poll&.to_h)
462
- Message.new(JSON.parse(response), self)
463
- end
464
-
465
- # Sends a text message to a channel given its ID and the message's content,
466
- # then deletes it after the specified timeout in seconds.
467
- # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
468
- # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
469
- # @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
470
- # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
471
- # @param embeds [Hash, OnyxCord::Webhooks::Embed, Array<Hash>, Array<OnyxCord::Webhooks::Embed> nil] The rich embed(s) to append to this message.
472
- # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
473
- # @param allowed_mentions [Hash, OnyxCord::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
474
- # @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
475
- # @param components [View, Array<Hash>] Interaction components to associate with this message.
476
- # @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2), SUPPRESS_NOTIFICATIONS (1 << 12), and IS_COMPONENTS_V2 (1 << 15) can be set.
477
- # @param nonce [String, nil] A optional nonce in order to verify that a message was sent. Maximum of twenty-five characters.
478
- # @param enforce_nonce [true, false] Whether the nonce should be enforced and used for message de-duplication.
479
- # @param poll [Hash, Poll::Builder, Poll, nil] The poll that should be attached to this message.
480
- def send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0, nonce = nil, enforce_nonce = false, poll = nil)
481
- OnyxCord::AsyncRuntime.async do
482
- message = send_message(channel, content, tts, embeds, attachments, allowed_mentions, message_reference, components, flags, nonce, enforce_nonce, poll)
483
- OnyxCord::AsyncRuntime.sleep(timeout)
484
- message.delete
485
- end
486
-
487
- nil
488
- end
489
-
490
- # Sends a file to a channel. If it is an image, it will automatically be embedded.
491
- # @note This executes in a blocking way, so if you're sending long files, be wary of delays.
492
- # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
493
- # @param file [File] The file that should be sent.
494
- # @param caption [string] The caption for the file.
495
- # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
496
- # @param filename [String] Overrides the filename of the uploaded file
497
- # @param spoiler [true, false] Whether or not this file should appear as a spoiler.
498
- # @example Send a file from disk
499
- # bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))
500
- def send_file(channel, file, caption: nil, tts: false, filename: nil, spoiler: nil)
501
- if file.respond_to?(:read)
502
- if spoiler
503
- filename ||= File.basename(file.path)
504
- filename = "SPOILER_#{filename}" unless filename.start_with? 'SPOILER_'
505
- end
506
- file.define_singleton_method(:original_filename) { filename } if filename
507
- file.define_singleton_method(:path) { filename } if filename
508
- end
509
-
510
- channel = channel.resolve_id
511
- response = API::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
512
- Message.new(JSON.parse(response), self)
513
- end
514
-
515
- # Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using
516
- # Discord. For information how to use this, see the docs: https://discord.com/developers/docs/topics/oauth2
517
- # @param name [String] What your application should be called.
518
- # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
519
- # @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
520
- def create_oauth_application(name, redirect_uris)
521
- response = JSON.parse(API.create_oauth_application(@token, name, redirect_uris))
522
- [response['id'], response['secret']]
523
- end
524
-
525
- # Changes information about your OAuth application
526
- # @param name [String] What your application should be called.
527
- # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
528
- # @param description [String] A string that describes what your application does.
529
- # @param icon [String, nil] A data URI for your icon image (for example a base 64 encoded image), or nil if no icon
530
- # should be set or changed.
531
- def update_oauth_application(name, redirect_uris, description = '', icon = nil)
532
- API.update_oauth_application(@token, name, redirect_uris, description, icon)
533
- end
534
-
535
- # Gets the users, channels, roles and emoji from a string.
536
- # @param mentions [String] The mentions, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
537
- # @param server [Server, nil] The server of the associated mentions. (recommended for role parsing, to speed things up)
538
- # @return [Array<User, Channel, Role, Emoji>] The array of users, channels, roles and emoji identified by the mentions, or `nil` if none exists.
539
- def parse_mentions(mentions, server = nil)
540
- array_to_return = []
541
- # While possible mentions may be in message
542
- while mentions.include?('<') && mentions.include?('>')
543
- # Removing all content before the next possible mention
544
- mentions = mentions.split('<', 2)[1]
545
- # Locate the first valid mention enclosed in `<...>`, otherwise advance to the next open `<`
546
- next unless mentions.split('>', 2).first.length < mentions.split('<', 2).first.length
547
-
548
- # Store the possible mention value to be validated with RegEx
549
- mention = mentions.split('>', 2).first
550
- if /@!?(?<id>\d+)/ =~ mention
551
- array_to_return << user(id) unless user(id).nil?
552
- elsif /#(?<id>\d+)/ =~ mention
553
- array_to_return << channel(id, server) unless channel(id, server).nil?
554
- elsif /@&(?<id>\d+)/ =~ mention
555
- if server
556
- array_to_return << server.role(id) unless server.role(id).nil?
557
- else
558
- @servers.each_value do |element|
559
- array_to_return << element.role(id) unless element.role(id).nil?
560
- end
561
- end
562
- elsif /(?<animated>^a|^${0}):(?<name>\w+):(?<id>\d+)/ =~ mention
563
- array_to_return << (emoji(id) || Emoji.new({ 'animated' => animated != '', 'name' => name, 'id' => id }, self, nil))
564
- end
565
- end
566
- array_to_return
567
- end
568
-
569
- # Gets the user, channel, role or emoji from a string.
570
- # @param mention [String] The mention, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
571
- # @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up)
572
- # @return [User, Channel, Role, Emoji] The user, channel, role or emoji identified by the mention, or `nil` if none exists.
573
- def parse_mention(mention, server = nil)
574
- parse_mentions(mention, server).first
575
- end
576
-
577
- # Updates presence status.
578
- # @param status [String] The status the bot should show up as. Can be `online`, `dnd`, `idle`, or `invisible`
579
- # @param activity [String, nil] The name of the activity to be played/watched/listened to/stream name on the stream.
580
- # @param url [String, nil] The Twitch URL to display as a stream. nil for no stream.
581
- # @param since [Integer] When this status was set.
582
- # @param afk [true, false] Whether the bot is AFK.
583
- # @param activity_type [Integer] The type of activity status to display.
584
- # Can be 0 (Playing), 1 (Streaming), 2 (Listening), 3 (Watching), or 5 (Competing).
585
- # @see Gateway#send_status_update
586
- def update_status(status, activity, url, since = 0, afk = false, activity_type = 0)
587
- gateway_check
588
-
589
- @activity = activity
590
- @status = status
591
- @streamurl = url
592
- type = url ? 1 : activity_type
593
-
594
- activity_obj = activity || url ? { 'name' => activity, 'url' => url, 'type' => type } : nil
595
- @gateway.send_status_update(status, since, activity_obj, afk)
596
-
597
- # Update the status in the cache
598
- profile.update_presence('status' => status.to_s, 'activities' => [activity_obj].compact)
599
- end
600
-
601
- # Sets the currently playing game to the specified game.
602
- # @param name [String] The name of the game to be played.
603
- # @return [String] The game that is being played now.
604
- def game=(name)
605
- gateway_check
606
- update_status(@status, name, nil)
607
- end
608
-
609
- alias_method :playing=, :game=
610
-
611
- # Sets the current listening status to the specified name.
612
- # @param name [String] The thing to be listened to.
613
- # @return [String] The thing that is now being listened to.
614
- def listening=(name)
615
- gateway_check
616
- update_status(@status, name, nil, nil, nil, 2)
617
- end
618
-
619
- # Sets the current watching status to the specified name.
620
- # @param name [String] The thing to be watched.
621
- # @return [String] The thing that is now being watched.
622
- def watching=(name)
623
- gateway_check
624
- update_status(@status, name, nil, nil, nil, 3)
625
- end
626
-
627
- # Sets the currently online stream to the specified name and Twitch URL.
628
- # @param name [String] The name of the stream to display.
629
- # @param url [String] The url of the current Twitch stream.
630
- # @return [String] The stream name that is being displayed now.
631
- def stream(name, url)
632
- gateway_check
633
- update_status(@status, name, url)
634
- name
635
- end
636
-
637
- # Sets the currently competing status to the specified name.
638
- # @param name [String] The name of the game to be competing in.
639
- # @return [String] The game that is being competed in now.
640
- def competing=(name)
641
- gateway_check
642
- update_status(@status, name, nil, nil, nil, 5)
643
- end
644
-
645
- # Sets status to online.
646
- def online
647
- gateway_check
648
- update_status(:online, @activity, @streamurl)
649
- end
650
-
651
- alias_method :on, :online
652
-
653
- # Sets status to idle.
654
- def idle
655
- gateway_check
656
- update_status(:idle, @activity, nil)
657
- end
658
-
659
- alias_method :away, :idle
660
-
661
- # Sets the bot's status to DnD (red icon).
662
- def dnd
663
- gateway_check
664
- update_status(:dnd, @activity, nil)
665
- end
666
-
667
- # Sets the bot's status to invisible (appears offline).
668
- def invisible
669
- gateway_check
670
- update_status(:invisible, @activity, nil)
671
- end
672
-
673
- # Join a thread
674
- # @param channel [Channel, Integer, String]
675
- def join_thread(channel)
676
- API::Channel.join_thread(@token, channel.resolve_id)
677
- nil
678
- end
679
-
680
- # Leave a thread
681
- # @param channel [Channel, Integer, String]
682
- def leave_thread(channel)
683
- API::Channel.leave_thread(@token, channel.resolve_id)
684
- nil
685
- end
686
-
687
- # Add a member to a thread
688
- # @param channel [Channel, Integer, String]
689
- # @param member [Member, Integer, String]
690
- def add_thread_member(channel, member)
691
- API::Channel.add_thread_member(@token, channel.resolve_id, member.resolve_id)
692
- nil
693
- end
694
-
695
- # Remove a member from a thread
696
- # @param channel [Channel, Integer, String]
697
- # @param member [Member, Integer, String]
698
- def remove_thread_member(channel, member)
699
- API::Channel.remove_thread_member(@token, channel.resolve_id, member.resolve_id)
700
- nil
701
- end
702
-
703
- # Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
704
- def debug=(new_debug)
705
- LOGGER.debug = new_debug
706
- end
707
-
708
- # Sets the logging mode
709
- # @see Logger#mode=
710
- def mode=(new_mode)
711
- LOGGER.mode = new_mode
712
- end
713
-
714
- def runtime_stats
715
- {
716
- mode: @mode,
717
- cache: cache_stats,
718
- event_executor: @event_executor.class.name,
719
- event_threads: @event_threads&.count(&:alive?) || 0,
720
- event_queue_size: @event_executor.respond_to?(:queue_size) ? @event_executor.queue_size : 0
721
- }
722
- end
723
-
724
- # Prevents the READY packet from being printed regardless of debug mode.
725
- def suppress_ready_debug
726
- @prevent_ready = true
727
- end
728
-
729
- # Add an await the bot should listen to. For information on awaits, see {Await}.
730
- # @param key [Symbol] The key that uniquely identifies the await for {AwaitEvent}s to listen to (see {#await}).
731
- # @param type [Class] The event class that should be listened for.
732
- # @param attributes [Hash] The attributes the event should check for. The block will only be executed if all attributes match.
733
- # @yield Is executed when the await is triggered.
734
- # @yieldparam event [Event] The event object that was triggered.
735
- # @return [Await] The await that was created.
736
- # @deprecated Will be changed to blocking behavior in v4.0. Use {#add_await!} instead.
737
- def add_await(key, type, attributes = {}, &block)
738
- raise "You can't await an AwaitEvent!" if type == OnyxCord::Events::AwaitEvent
739
-
740
- await = Await.new(self, key, type, attributes, block)
741
- @awaits ||= {}
742
- @awaits[key] = await
743
- end
744
-
745
- # Awaits an event, blocking the current thread until a response is received.
746
- # @param type [Class] The event class that should be listened for.
747
- # @option attributes [Numeric] :timeout the amount of time (in seconds) to wait for a response before returning `nil`. Waits forever if omitted.
748
- # @yield Executed when a matching event is received.
749
- # @yieldparam event [Event] The event object that was triggered.
750
- # @yieldreturn [true, false] Whether the event matches extra await criteria described by the block
751
- # @return [Event, nil] The event object that was triggered, or `nil` if a `timeout` was set and no event was raised in time.
752
- # @raise [ArgumentError] if `timeout` is given and is not a positive numeric value
753
- def add_await!(type, attributes = {})
754
- raise "You can't await an AwaitEvent!" if type == OnyxCord::Events::AwaitEvent
755
-
756
- timeout = attributes[:timeout]
757
- raise ArgumentError, 'Timeout must be a number > 0' if timeout.is_a?(Numeric) && !timeout.positive?
758
-
759
- mutex = Mutex.new
760
- cv = ConditionVariable.new
761
- response = nil
762
- block = lambda do |event|
763
- mutex.synchronize do
764
- response = event
765
- if block_given?
766
- result = yield(event)
767
- cv.signal if result.is_a?(TrueClass)
768
- else
769
- cv.signal
770
- end
771
- end
772
- end
773
-
774
- handler = register_event(type, attributes, block)
775
-
776
- if timeout
777
- Thread.new do
778
- sleep timeout
779
- mutex.synchronize { cv.signal }
780
- end
781
- end
782
-
783
- mutex.synchronize { cv.wait(mutex) }
784
-
785
- remove_handler(handler)
786
- raise 'ConditionVariable was signaled without returning an event!' if response.nil? && timeout.nil?
787
-
788
- response
789
- end
790
-
791
- # Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.
792
- # @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and
793
- # presence and any other events will still be received.
794
- # @param user [User, String, Integer] The user, or its ID, to be ignored.
795
- def ignore_user(user)
796
- @ignored_ids << user.resolve_id
797
- end
798
-
799
- # Remove a user from the ignore list.
800
- # @param user [User, String, Integer] The user, or its ID, to be unignored.
801
- def unignore_user(user)
802
- @ignored_ids.delete(user.resolve_id)
803
- end
804
-
805
- # Checks whether a user is being ignored.
806
- # @param user [User, String, Integer] The user, or its ID, to check.
807
- # @return [true, false] whether or not the user is ignored.
808
- def ignored?(user)
809
- @ignored_ids.include?(user.resolve_id)
810
- end
811
-
812
- # @see Logger#debug
813
- def debug(message)
814
- LOGGER.debug(message)
815
- end
816
-
817
- # @see Logger#log_exception
818
- def log_exception(e)
819
- LOGGER.log_exception(e)
820
- end
821
-
822
- # Dispatches an event to this bot. Called by the gateway connection handler used internally.
823
- def dispatch(type, data = nil)
824
- return dispatch_packet(type) if data.nil? && type.is_a?(Hash)
825
-
826
- handle_dispatch(type, data)
827
- end
828
-
829
- # Raises a heartbeat event. Called by the gateway connection handler used internally.
830
- def raise_heartbeat_event
831
- raise_event(HeartbeatEvent.new(self))
832
- end
833
-
834
- # Makes the bot leave any groups with no recipients remaining
835
- def prune_empty_groups
836
- @channels.each_value do |channel|
837
- channel.leave_group if channel.group? && channel.recipients.empty?
838
- end
839
- end
840
-
841
- # Get all application commands.
842
- # @param server_id [String, Integer, nil] The ID of the server to get the commands from. Global if `nil`.
843
- # @return [Array<ApplicationCommand>]
844
- def get_application_commands(server_id: nil)
845
- resp = if server_id
846
- API::Application.get_guild_commands(@token, profile.id, server_id)
847
- else
848
- API::Application.get_global_commands(@token, profile.id)
849
- end
850
-
851
- JSON.parse(resp).map do |command_data|
852
- ApplicationCommand.new(command_data, self, server_id)
853
- end
854
- end
855
-
856
- # Get an application command by ID.
857
- # @param command_id [String, Integer]
858
- # @param server_id [String, Integer, nil] The ID of the server to get the command from. Global if `nil`.
859
- def get_application_command(command_id, server_id: nil)
860
- resp = if server_id
861
- API::Application.get_guild_command(@token, profile.id, server_id, command_id)
862
- else
863
- API::Application.get_global_command(@token, profile.id, command_id)
864
- end
865
- ApplicationCommand.new(JSON.parse(resp), self, server_id)
866
- end
867
-
868
- # @return [OnyxCord::ApplicationCommands::Registry]
869
- def commands
870
- @modern_command_registry ||= OnyxCord::ApplicationCommands::Registry.new(self)
871
- end
872
-
873
- def slash(name, description: nil, **attributes, &block)
874
- commands.slash(name, description: description, **attributes, &block)
875
- end
876
-
877
- def user_command(name, **attributes, &block)
878
- commands.user(name, **attributes, &block)
879
- end
880
-
881
- def message_command(name, **attributes, &block)
882
- commands.message(name, **attributes, &block)
883
- end
884
-
885
- def sync_application_commands!(server_id: nil, delete_unknown: false)
886
- commands.sync!(server_id: server_id, delete_unknown: delete_unknown)
887
- end
888
-
889
- def bulk_overwrite_global_application_commands(commands)
890
- response = API::Application.bulk_overwrite_global_commands(@token, profile.id, commands)
891
- JSON.parse(response).map { |data| ApplicationCommand.new(data, self) }
892
- end
893
-
894
- def bulk_overwrite_guild_application_commands(server_id, commands)
895
- response = API::Application.bulk_overwrite_guild_commands(@token, profile.id, server_id.resolve_id, commands)
896
- JSON.parse(response).map { |data| ApplicationCommand.new(data, self, server_id) }
897
- end
898
-
899
- # @yieldparam [OptionBuilder]
900
- # @yieldparam [PermissionBuilder]
901
- # @example
902
- # bot.register_application_command(:reddit, 'Reddit Commands') do |cmd|
903
- # cmd.subcommand_group(:subreddit, 'Subreddit Commands') do |group|
904
- # group.subcommand(:hot, "What's trending") do |sub|
905
- # sub.string(:subreddit, 'Subreddit to search')
906
- # end
907
- # group.subcommand(:new, "What's new") do |sub|
908
- # sub.string(:since, 'How long ago', choices: ['this hour', 'today', 'this week', 'this month', 'this year', 'all time'])
909
- # sub.string(:subreddit, 'Subreddit to search')
910
- # end
911
- # end
912
- # end
913
- def register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input, default_member_permissions: nil, contexts: nil, nsfw: false, integration_types: nil)
914
- type = ApplicationCommand::TYPES[type] || type
915
-
916
- contexts = contexts&.map { |context| Interaction::CONTEXTS[context] || context }
917
- integration_types = integration_types&.map { |type| Interaction::INTEGRATION_TYPES[type] || type }
918
- default_member_permissions = Permissions.bits(default_member_permissions) if default_member_permissions.is_a?(Array)
919
-
920
- builder = Interactions::OptionBuilder.new
921
- permission_builder = Interactions::PermissionBuilder.new
922
- yield(builder, permission_builder) if block_given?
923
-
924
- resp = if server_id
925
- API::Application.create_guild_command(@token, profile.id, server_id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw)
926
- else
927
- API::Application.create_global_command(@token, profile.id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw, integration_types)
928
- end
929
- cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
930
-
931
- if permission_builder.to_a.any?
932
- raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
933
-
934
- edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
935
- end
936
-
937
- cmd
938
- end
939
-
940
- # @yieldparam [OptionBuilder]
941
- # @yieldparam [PermissionBuilder]
942
- def edit_application_command(command_id, server_id: nil, name: nil, description: nil, default_permission: nil, type: :chat_input, default_member_permissions: nil, contexts: nil, nsfw: nil, integration_types: nil)
943
- type = ApplicationCommand::TYPES[type] || type
944
-
945
- contexts = contexts&.map { |context| Interaction::CONTEXTS[context] || context }
946
- integration_types = integration_types&.map { |type| Interaction::INTEGRATION_TYPES[type] || type }
947
- default_member_permissions = Permissions.bits(default_member_permissions) if default_member_permissions.is_a?(Array)
948
-
949
- builder = Interactions::OptionBuilder.new
950
- permission_builder = Interactions::PermissionBuilder.new
951
-
952
- yield(builder, permission_builder) if block_given?
953
-
954
- resp = if server_id
955
- API::Application.edit_guild_command(@token, profile.id, server_id, command_id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw)
956
- else
957
- API::Application.edit_global_command(@token, profile.id, command_id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw, integration_types)
958
- end
959
- cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
960
-
961
- if permission_builder.to_a.any?
962
- raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
963
-
964
- edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
965
- end
966
-
967
- cmd
968
- end
969
-
970
- # Remove an application command from the commands registered with discord.
971
- # @param command_id [String, Integer] The ID of the command to remove.
972
- # @param server_id [String, Integer] The ID of the server to delete this command from, global if `nil`.
973
- def delete_application_command(command_id, server_id: nil)
974
- if server_id
975
- API::Application.delete_guild_command(@token, profile.id, server_id, command_id)
976
- else
977
- API::Application.delete_global_command(@token, profile.id, command_id)
978
- end
979
- end
980
-
981
- # @param command_id [Integer, String]
982
- # @param server_id [Integer, String]
983
- # @param permissions [Array<Hash>] An array of objects formatted as `{ id: ENTITY_ID, type: 1 or 2, permission: true or false }`
984
- # @param bearer_token [String] A valid bearer token that has permission to manage the server and its roles.
985
- def edit_application_command_permissions(command_id, server_id, permissions = [], bearer_token = nil)
986
- builder = Interactions::PermissionBuilder.new
987
- yield builder if block_given?
988
-
989
- raise ArgumentError, 'This method requires a valid bearer token to be provided' unless bearer_token
990
-
991
- permissions += builder.to_a
992
- bearer_token = "Bearer #{bearer_token.delete_prefix('Bearer ')}"
993
- API::Application.edit_guild_command_permissions(bearer_token, profile.id, server_id, command_id, permissions)
994
- end
995
-
996
- # Get the permissions for all of the application commands in a specific server.
997
- # @param server_id [Integer, String, nil] The ID of the server to fetch application command permissions for.
998
- # @return [Array<ApplicationCommand::Permission>] The permissions for all of the application commands in the given server.
999
- def application_command_permissions(server_id:)
1000
- response = API::Application.get_guild_application_command_permissions(@token, profile.id, server_id.resolve_id)
1001
- JSON.parse(response).flat_map { |data| data['permissions'].map { |inner| ApplicationCommand::Permission.new(inner, data, self) } }
1002
- end
1003
-
1004
- # Fetches all the application emojis that the bot can use.
1005
- # @return [Array<Emoji>] Returns an array of emoji objects.
1006
- def application_emojis
1007
- response = API::Application.list_application_emojis(@token, profile.id)
1008
- JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) }
1009
- end
1010
-
1011
- # Fetches a single application emoji from its ID.
1012
- # @param emoji_id [Integer, String] ID of the application emoji.
1013
- # @return [Emoji] The application emoji.
1014
- def application_emoji(emoji_id)
1015
- response = API::Application.get_application_emoji(@token, profile.id, emoji_id.resolve_id)
1016
- Emoji.new(JSON.parse(response), self)
1017
- end
1018
-
1019
- # Creates a new custom emoji that can be used by this application.
1020
- # @param name [String] The name of emoji to create.
1021
- # @param image [String, #read] Base64 string with the image data, or an object that responds to #read.
1022
- # @return [Emoji] The emoji that has been created.
1023
- def create_application_emoji(name:, image:)
1024
- image = image.respond_to?(:read) ? OnyxCord.encode64(image) : image
1025
- response = API::Application.create_application_emoji(@token, profile.id, name, image)
1026
- Emoji.new(JSON.parse(response), self)
1027
- end
1028
-
1029
- # Edits an existing application emoji.
1030
- # @param emoji_id [Integer, String, Emoji] ID of the application emoji to edit.
1031
- # @param name [String] The new name of the emoji.
1032
- # @return [Emoji] Returns the updated emoji object on success.
1033
- def edit_application_emoji(emoji_id, name:)
1034
- response = API::Application.edit_application_emoji(@token, profile.id, emoji_id.resolve_id, name)
1035
- Emoji.new(JSON.parse(response), self)
1036
- end
1037
-
1038
- # Deletes an existing application emoji.
1039
- # @param emoji_id [Integer, String, Emoji] ID of the application emoji to delete.
1040
- def delete_application_emoji(emoji_id)
1041
- API::Application.delete_application_emoji(@token, profile.id, emoji_id.resolve_id)
1042
- end
1043
-
1044
274
  # @!visibility private
1045
275
  def inspect
1046
276
  "<Bot client_id=#{@client_id.inspect} redact_token=#{@redact_token.inspect}>"
1047
277
  end
1048
-
1049
- private
1050
-
1051
- # Throws a useful exception if there's currently no gateway connection.
1052
- def gateway_check
1053
- raise "A gateway connection is necessary to call this method! You'll have to do it inside any event (e.g. `ready`) or after `bot.run :async`." unless connected?
1054
- end
1055
-
1056
- # Logs a warning if there are servers which are still unavailable.
1057
- # e.g. due to a Discord outage or because the servers are large and taking a while to load.
1058
- def unavailable_servers_check
1059
- # Return unless there are servers that are unavailable.
1060
- return unless @unavailable_servers&.positive?
1061
-
1062
- LOGGER.warn("#{@unavailable_servers} servers haven't been cached yet.")
1063
- LOGGER.warn('Servers may be unavailable due to an outage, or your bot is on very large servers that are taking a while to load.')
1064
- end
1065
-
1066
- ### ## ## ######## ######## ######## ## ## ### ## ######
1067
- ## ### ## ## ## ## ## ### ## ## ## ## ## ##
1068
- ## #### ## ## ## ## ## #### ## ## ## ## ##
1069
- ## ## ## ## ## ###### ######## ## ## ## ## ## ## ######
1070
- ## ## #### ## ## ## ## ## #### ######### ## ##
1071
- ## ## ### ## ## ## ## ## ### ## ## ## ## ##
1072
- ### ## ## ## ######## ## ## ## ## ## ## ######## ######
1073
-
1074
- # Internal handler for PRESENCE_UPDATE
1075
- def update_presence(data)
1076
- # Friends list presences have no server ID so ignore these to not cause an error
1077
- return unless data['guild_id']
1078
-
1079
- user_id = data['user']['id'].to_i
1080
- server_id = data['guild_id'].to_i
1081
- server = server(server_id)
1082
- return unless server
1083
-
1084
- member_is_new = false
1085
-
1086
- if server.member_cached?(user_id)
1087
- member = server.member(user_id)
1088
- else
1089
- # If the member is not cached yet, it means that it just came online from not being cached at all
1090
- # due to large_threshold. Fortunately, Discord sends the entire member object in this case, and
1091
- # not just a part of it - we can just cache this member directly
1092
- member = Member.new(data, server, self)
1093
- debug("Implicitly adding presence-obtained member #{user_id} to #{server_id} cache")
1094
-
1095
- member_is_new = true
1096
- end
1097
-
1098
- username = data['user']['username']
1099
- if username && !member_is_new # Don't set the username for newly-cached members
1100
- debug "Implicitly updating presence-obtained information username for member #{user_id}"
1101
- member.update_username(username)
1102
- end
1103
-
1104
- global_name = data['user']['global_name']
1105
- if global_name && !member_is_new # Don't set the global_name for newly-cached members
1106
- debug "Implicitly updating presence-obtained information global_name for member #{user_id}"
1107
- member.update_global_name(global_name)
1108
- end
1109
-
1110
- member.update_presence(data)
1111
-
1112
- member.avatar_id = data['user']['avatar'] if data['user']['avatar']
1113
-
1114
- server.cache_member(member)
1115
- end
1116
-
1117
- # Internal handler for VOICE_STATE_UPDATE
1118
- def update_voice_state(data)
1119
- @session_id = data['session_id']
1120
-
1121
- server_id = data['guild_id'].to_i
1122
- server = @servers&.[](server_id)
1123
- return unless server
1124
-
1125
- user_id = data['user_id'].to_i
1126
- old_voice_state = server.voice_states[user_id]
1127
- old_channel_id = old_voice_state&.channel_id || old_voice_state&.voice_channel&.id
1128
-
1129
- server.update_voice_state(data)
1130
-
1131
- existing_voice = @voices[server_id]
1132
- if user_id == @profile.id && existing_voice
1133
- new_channel_id = data['channel_id']
1134
- if new_channel_id
1135
- channel_id = new_channel_id.to_i
1136
- new_channel = @channels&.[](channel_id) || server.channels.find { |channel| channel.id == channel_id }
1137
- existing_voice.channel = new_channel
1138
- else
1139
- voice_destroy(server_id)
1140
- end
1141
- end
1142
-
1143
- old_channel_id
1144
- end
1145
-
1146
- # Internal handler for VOICE_SERVER_UPDATE
1147
- def update_voice_server(data)
1148
- server_id = data['guild_id'].to_i
1149
- channel = @should_connect_to_voice[server_id]
1150
-
1151
- debug("Voice server update received! chan: #{channel.inspect}")
1152
- return unless channel
1153
-
1154
- @should_connect_to_voice.delete(server_id)
1155
- debug('Updating voice server!')
1156
-
1157
- token = data['token']
1158
- endpoint = data['endpoint']
1159
-
1160
- unless endpoint
1161
- debug('VOICE_SERVER_UPDATE sent with nil endpoint! Ignoring')
1162
- return
1163
- end
1164
-
1165
- debug('Got data, now creating the bot.')
1166
- @voices[server_id] = OnyxCord::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint)
1167
- end
1168
-
1169
- # Internal handler for CHANNEL_CREATE
1170
- def create_channel(data)
1171
- channel = data.is_a?(OnyxCord::Channel) ? data : Channel.new(data, self)
1172
- server = channel.server
1173
-
1174
- # The last message ID of a forum channel is the most recent post
1175
- channel.parent.process_last_message_id(channel.id) if channel.parent&.forum? || channel.parent&.media?
1176
-
1177
- # Handle normal and private channels separately
1178
- if server
1179
- server.add_channel(channel)
1180
- @channels[channel.id] = channel if @channels
1181
- elsif channel.private?
1182
- @pm_channels[channel.recipient.id] = channel if @pm_channels
1183
- elsif channel.group?
1184
- @channels[channel.id] = channel if @channels
1185
- end
1186
- end
1187
-
1188
- # Internal handler for CHANNEL_UPDATE
1189
- def update_channel(data)
1190
- @channels&.[](data['id'].to_i)&.update_data(data)
1191
- end
1192
-
1193
- # Internal handler for CHANNEL_DELETE
1194
- def delete_channel(data)
1195
- channel = Channel.new(data, self)
1196
- server = channel.server
1197
-
1198
- # Handle normal and private channels separately
1199
- if server
1200
- @channels&.delete(channel.id)
1201
- server.delete_channel(channel.id)
1202
- elsif channel.pm?
1203
- @pm_channels&.delete(channel.recipient.id)
1204
- elsif channel.group?
1205
- @channels&.delete(channel.id)
1206
- end
1207
-
1208
- @thread_members&.delete(channel.id) if channel.thread?
1209
- end
1210
-
1211
- # Internal handler for GUILD_MEMBER_ADD
1212
- def add_guild_member(data)
1213
- server_id = data['guild_id'].to_i
1214
- server = self.server(server_id)
1215
-
1216
- member = Member.new(data, server, self)
1217
- server.add_member(member)
1218
- end
1219
-
1220
- # Internal handler for GUILD_MEMBER_UPDATE
1221
- def update_guild_member(data)
1222
- server_id = data['guild_id'].to_i
1223
- server = self.server(server_id)
1224
-
1225
- # Only attempt to update members that're already cached
1226
- if (member = server.member(data['user']['id'].to_i, false))
1227
- member.update_data(data)
1228
- else
1229
- ensure_user(data['user'])
1230
- end
1231
- end
1232
-
1233
- # Internal handler for GUILD_MEMBER_DELETE
1234
- def delete_guild_member(data)
1235
- server_id = data['guild_id'].to_i
1236
- server = self.server(server_id)
1237
- return unless server
1238
-
1239
- user_id = data['user']['id'].to_i
1240
- server.delete_member(user_id)
1241
- rescue OnyxCord::Errors::NoPermission
1242
- OnyxCord::LOGGER.warn("delete_guild_member attempted to access a server for which the bot doesn't have permission! Not sure what happened here, ignoring")
1243
- end
1244
-
1245
- # Internal handler for GUILD_CREATE
1246
- def create_guild(data)
1247
- ensure_server(data, true)
1248
- end
1249
-
1250
- # Internal handler for GUILD_UPDATE
1251
- def update_guild(data)
1252
- server_id = data['id'].to_i
1253
- server = @servers&.[](server_id)
1254
-
1255
- if server
1256
- server.update_data(data)
1257
- else
1258
- LOGGER.warn("GUILD_UPDATE received for uncached server #{server_id}; caching from payload")
1259
- ensure_server(data, true)
1260
- end
1261
- end
1262
-
1263
- # Internal handler for GUILD_DELETE
1264
- def delete_guild(data)
1265
- id = data['id'].to_i
1266
- @servers.delete(id)
1267
- end
1268
-
1269
- # Internal handler for GUILD_ROLE_CREATE and GUILD_ROLE_UPDATE
1270
- def update_guild_role(data)
1271
- server = @servers[data['guild_id'].to_i]
1272
-
1273
- if (role = server&.role(data['role']['id'].to_i))
1274
- role.update_data(data['role'])
1275
- else
1276
- server&.add_role(Role.new(data['role'], self, server))
1277
- end
1278
- end
1279
-
1280
- # Internal handler for GUILD_ROLE_DELETE
1281
- def delete_guild_role(data)
1282
- role_id = data['role_id'].to_i
1283
- server_id = data['guild_id'].to_i
1284
- server = @servers[server_id]
1285
- server&.delete_role(role_id)
1286
- end
1287
-
1288
- # Internal handler for GUILD_EMOJIS_UPDATE
1289
- def update_guild_emoji(data)
1290
- server_id = data['guild_id'].to_i
1291
- server = @servers[server_id]
1292
- server&.update_emoji_data(data)
1293
- end
1294
-
1295
- # Internal handler for GUILD_SCHEDULED_EVENT_CREATE and GUILD_SCHEDULED_EVENT_UPDATE
1296
- def update_guild_scheduled_event(data)
1297
- server = @servers[data['guild_id'].to_i]
1298
-
1299
- if (event = server&.scheduled_event(data['id'].to_i, request: false))
1300
- event&.update_data(data)
1301
- else
1302
- server&.cache_scheduled_event(ScheduledEvent.new(data, server, self))
1303
- end
1304
- end
1305
-
1306
- # Internal handler for MESSAGE_CREATE
1307
- def create_message(data); end
1308
-
1309
- # Internal handler for TYPING_START
1310
- def start_typing(data); end
1311
-
1312
- # Internal handler for MESSAGE_UPDATE
1313
- def update_message(data); end
1314
-
1315
- # Internal handler for MESSAGE_DELETE
1316
- def delete_message(data); end
1317
-
1318
- # Internal handler for MESSAGE_REACTION_ADD
1319
- def add_message_reaction(data); end
1320
-
1321
- # Internal handler for MESSAGE_REACTION_REMOVE
1322
- def remove_message_reaction(data); end
1323
-
1324
- # Internal handler for MESSAGE_REACTION_REMOVE_ALL
1325
- def remove_all_message_reactions(data); end
1326
-
1327
- # Internal handler for GUILD_BAN_ADD
1328
- def add_user_ban(data); end
1329
-
1330
- # Internal handler for GUILD_BAN_REMOVE
1331
- def remove_user_ban(data); end
1332
-
1333
- ## ####### ###### #### ## ##
1334
- ## ## ## ## ## ## ### ##
1335
- ## ## ## ## ## #### ##
1336
- ## ## ## ## #### ## ## ## ##
1337
- ## ## ## ## ## ## ## ####
1338
- ## ## ## ## ## ## ## ###
1339
- ######## ####### ###### #### ## ##
1340
-
1341
- def process_token(type, token)
1342
- # Remove the "Bot " prefix if it exists
1343
- token = token[4..] if token.start_with? 'Bot '
1344
-
1345
- token = "Bot #{token}" unless type == :user
1346
- token
1347
- end
1348
-
1349
- def handle_dispatch(type, data)
1350
- # Check whether there are still unavailable servers and there have been more than 10 seconds since READY
1351
- if @unavailable_servers&.positive? && (Time.now - @unavailable_timeout_time) > 10 && !(@intents || 0).nobits?(INTENTS[:servers])
1352
- # The server streaming timed out!
1353
- LOGGER.debug("Server streaming timed out with #{@unavailable_servers} servers remaining")
1354
- LOGGER.debug('Calling ready now because server loading is taking a long time. Servers may be unavailable due to an outage, or your bot is on very large servers.')
1355
-
1356
- # Unset the unavailable server count so this doesn't get triggered again
1357
- @unavailable_servers = 0
1358
-
1359
- notify_ready
1360
- end
1361
-
1362
- case type
1363
- when :READY
1364
- # As READY may be called multiple times over a single process lifetime, we here need to reset the cache entirely
1365
- # to prevent possible inconsistencies, like objects referencing old versions of other objects which have been
1366
- # replaced.
1367
- init_cache
1368
-
1369
- @profile = Profile.new(data['user'], self)
1370
-
1371
- @client_id ||= data['application']['id']&.to_i
1372
-
1373
- # Initialize servers
1374
- @servers = {}
1375
-
1376
- # Count unavailable servers
1377
- @unavailable_servers = 0
1378
-
1379
- data['guilds'].each do |element|
1380
- # Check for true specifically because unavailable=false indicates that a previously unavailable server has
1381
- # come online
1382
- if element['unavailable']
1383
- @unavailable_servers += 1
1384
-
1385
- # Ignore any unavailable servers
1386
- next
1387
- end
1388
-
1389
- ensure_server(element, true)
1390
- end
1391
-
1392
- # Don't notify yet if there are unavailable servers because they need to get available before the bot truly has
1393
- # all the data
1394
- if @unavailable_servers.zero?
1395
- # No unavailable servers - we're ready!
1396
- notify_ready
1397
- end
1398
-
1399
- @ready_time = Time.now
1400
- @unavailable_timeout_time = Time.now
1401
- when :GUILD_MEMBERS_CHUNK
1402
- id = data['guild_id'].to_i
1403
- server = server(id)
1404
- server.process_chunk(data['members'], data['chunk_index'], data['chunk_count'])
1405
- when :USER_UPDATE
1406
- @profile = Profile.new(data, self)
1407
- when :INVITE_CREATE
1408
- invite = Invite.new(data, self)
1409
- raise_event(InviteCreateEvent.new(data, invite, self))
1410
- when :INVITE_DELETE
1411
- raise_event(InviteDeleteEvent.new(data, self))
1412
- when :MESSAGE_CREATE
1413
- if ignored?(data['author']['id'])
1414
- debug("Ignored author with ID #{data['author']['id']}")
1415
- return
1416
- end
1417
-
1418
- if @ignore_bots && data['author']['bot']
1419
- debug("Ignored Bot account with ID #{data['author']['id']}")
1420
- return
1421
- end
1422
-
1423
- if !should_parse_self && profile.id == data['author']['id'].to_i
1424
- debug('Ignored message from the current bot')
1425
- return
1426
- end
1427
-
1428
- # If create_message is overwritten with a method that returns the parsed message, use that instead, so we don't
1429
- # parse the message twice (which is just thrown away performance)
1430
- message = create_message(data)
1431
- message = Message.new(data, self) unless message.is_a? Message
1432
-
1433
- # Update the existing member if it exists in the cache.
1434
- if data['member']
1435
- member = message.channel.server&.member(data['author']['id'].to_i, false)
1436
- data['member']['user'] = data['author']
1437
- member&.update_data(data['member'])
1438
- end
1439
-
1440
- # Dispatch a ChannelCreateEvent for channels we don't have cached
1441
- if message.channel.private? && !@pm_channels&.key?(message.channel.recipient.id)
1442
- create_channel(message.channel)
1443
-
1444
- raise_event(ChannelCreateEvent.new(message.channel, self))
1445
- end
1446
-
1447
- message.channel.process_last_message_id(message.id)
1448
-
1449
- event = MessageEvent.new(message, self)
1450
- raise_event(event)
1451
-
1452
- # Raise a mention event for any direct mentions.
1453
- if message.mentions.any? { |user| user.id == profile.id }
1454
- event = MentionEvent.new(message, self, false)
1455
- raise_event(event)
1456
- end
1457
-
1458
- # Raise a mention event for the current bot's auto-generated role.
1459
- if message.role_mentions.any? { |role| role.tags&.bot_id == profile.id }
1460
- event = MentionEvent.new(message, self, true)
1461
- raise_event(event)
1462
- end
1463
-
1464
- if message.channel.private?
1465
- event = PrivateMessageEvent.new(message, self)
1466
- raise_event(event)
1467
- end
1468
- when :MESSAGE_UPDATE
1469
- update_message(data)
1470
-
1471
- if !should_parse_self && profile.id == data['author']['id'].to_i
1472
- debug('Ignored message from the current bot')
1473
- return
1474
- end
1475
-
1476
- message = Message.new(data, self)
1477
-
1478
- event = MessageUpdateEvent.new(message, self)
1479
- raise_event(event)
1480
-
1481
- if data['author'].nil?
1482
- LOGGER.debug("Edited a message with nil author! Content: #{message.content.inspect}, channel: #{message.channel.inspect}")
1483
- return
1484
- end
1485
-
1486
- # Update the existing member if it exists in the cache.
1487
- if data['member']
1488
- member = message.channel.server&.member(data['author']['id'].to_i, false)
1489
- data['member']['user'] = data['author']
1490
- member&.update_data(data['member'])
1491
- end
1492
-
1493
- event = MessageEditEvent.new(message, self)
1494
- raise_event(event)
1495
- when :MESSAGE_DELETE
1496
- delete_message(data)
1497
-
1498
- event = MessageDeleteEvent.new(data, self)
1499
- raise_event(event)
1500
- when :MESSAGE_DELETE_BULK
1501
- debug("MESSAGE_DELETE_BULK will raise #{data['ids'].length} events")
1502
-
1503
- data['ids'].each do |single_id|
1504
- # Form a data hash for a single ID so the methods get what they want
1505
- single_data = {
1506
- 'id' => single_id,
1507
- 'channel_id' => data['channel_id']
1508
- }
1509
-
1510
- # Raise as normal
1511
- delete_message(single_data)
1512
-
1513
- event = MessageDeleteEvent.new(single_data, self)
1514
- raise_event(event)
1515
- end
1516
- when :TYPING_START
1517
- start_typing(data)
1518
-
1519
- begin
1520
- event = TypingEvent.new(data, self)
1521
- raise_event(event)
1522
- rescue OnyxCord::Errors::NoPermission
1523
- debug 'Typing started in channel the bot has no access to, ignoring'
1524
- end
1525
- when :MESSAGE_REACTION_ADD
1526
- add_message_reaction(data)
1527
-
1528
- return if profile.id == data['user_id'].to_i && !should_parse_self
1529
-
1530
- if data['member']
1531
- server = self.server(data['guild_id'].to_i)
1532
-
1533
- server&.cache_member(Member.new(data['member'], server, self))
1534
- end
1535
-
1536
- event = ReactionAddEvent.new(data, self)
1537
- raise_event(event)
1538
- when :MESSAGE_REACTION_REMOVE
1539
- remove_message_reaction(data)
1540
-
1541
- return if profile.id == data['user_id'].to_i && !should_parse_self
1542
-
1543
- event = ReactionRemoveEvent.new(data, self)
1544
- raise_event(event)
1545
- when :MESSAGE_REACTION_REMOVE_ALL
1546
- remove_all_message_reactions(data)
1547
-
1548
- event = ReactionRemoveAllEvent.new(data, self)
1549
- raise_event(event)
1550
- when :MESSAGE_REACTION_REMOVE_EMOJI
1551
-
1552
- event = ReactionRemoveEmojiEvent.new(data, self)
1553
- raise_event(event)
1554
- when :PRESENCE_UPDATE
1555
- # Ignore friends list presences
1556
- return unless data['guild_id']
1557
-
1558
- new_activities = (data['activities'] || []).map { |act_data| Activity.new(act_data, self) }
1559
- presence_user = @users[data['user']['id'].to_i]
1560
- old_activities = (presence_user&.activities || [])
1561
- update_presence(data)
1562
-
1563
- # Starting a new game
1564
- playing_change = new_activities.reject do |act|
1565
- old_activities.find { |old| old.name == act.name }
1566
- end
1567
-
1568
- # Exiting an existing game
1569
- playing_change += old_activities.reject do |old|
1570
- new_activities.find { |act| act.name == old.name }
1571
- end
1572
-
1573
- if playing_change.any?
1574
- playing_change.each do |act|
1575
- raise_event(PlayingEvent.new(data, act, self))
1576
- end
1577
- else
1578
- raise_event(PresenceEvent.new(data, self))
1579
- end
1580
- when :VOICE_STATE_UPDATE
1581
- old_channel_id = update_voice_state(data)
1582
-
1583
- event = VoiceStateUpdateEvent.new(data, old_channel_id, self)
1584
- raise_event(event)
1585
- when :VOICE_SERVER_UPDATE
1586
- update_voice_server(data)
1587
-
1588
- event = VoiceServerUpdateEvent.new(data, self)
1589
- raise_event(event)
1590
- when :CHANNEL_CREATE
1591
- create_channel(data)
1592
-
1593
- event = ChannelCreateEvent.new(data, self)
1594
- raise_event(event)
1595
- when :CHANNEL_UPDATE
1596
- update_channel(data)
1597
-
1598
- event = ChannelUpdateEvent.new(data, self)
1599
- raise_event(event)
1600
- when :CHANNEL_DELETE
1601
- delete_channel(data)
1602
-
1603
- event = ChannelDeleteEvent.new(data, self)
1604
- raise_event(event)
1605
- when :CHANNEL_PINS_UPDATE
1606
- event = ChannelPinsUpdateEvent.new(data, self)
1607
-
1608
- event.channel.process_last_pin_timestamp(data['last_pin_timestamp']) if data.key?('last_pin_timestamp')
1609
-
1610
- raise_event(event)
1611
- when :GUILD_MEMBER_ADD
1612
- add_guild_member(data)
1613
-
1614
- event = ServerMemberAddEvent.new(data, self)
1615
- raise_event(event)
1616
- when :GUILD_MEMBER_UPDATE
1617
- update_guild_member(data)
1618
-
1619
- event = ServerMemberUpdateEvent.new(data, self)
1620
- raise_event(event)
1621
- when :GUILD_MEMBER_REMOVE
1622
- delete_guild_member(data)
1623
-
1624
- event = ServerMemberDeleteEvent.new(data, self)
1625
- raise_event(event)
1626
- when :GUILD_AUDIT_LOG_ENTRY_CREATE
1627
- event = AuditLogEntryCreateEvent.new(data, self)
1628
- raise_event(event)
1629
- when :GUILD_BAN_ADD
1630
- add_user_ban(data)
1631
-
1632
- event = UserBanEvent.new(data, self)
1633
- raise_event(event)
1634
- when :GUILD_BAN_REMOVE
1635
- remove_user_ban(data)
1636
-
1637
- event = UserUnbanEvent.new(data, self)
1638
- raise_event(event)
1639
- when :GUILD_ROLE_UPDATE
1640
- update_guild_role(data)
1641
-
1642
- event = ServerRoleUpdateEvent.new(data, self)
1643
- raise_event(event)
1644
- when :GUILD_ROLE_CREATE
1645
- update_guild_role(data)
1646
-
1647
- event = ServerRoleCreateEvent.new(data, self)
1648
- raise_event(event)
1649
- when :GUILD_ROLE_DELETE
1650
- delete_guild_role(data)
1651
-
1652
- event = ServerRoleDeleteEvent.new(data, self)
1653
- raise_event(event)
1654
- when :INTEGRATION_CREATE
1655
- event = IntegrationCreateEvent.new(data, self)
1656
- raise_event(event)
1657
- when :INTEGRATION_UPDATE
1658
- event = IntegrationUpdateEvent.new(data, self)
1659
- raise_event(event)
1660
- when :INTEGRATION_DELETE
1661
- event = IntegrationDeleteEvent.new(data, self)
1662
- raise_event(event)
1663
- when :GUILD_CREATE
1664
- create_guild(data)
1665
-
1666
- # Check for false specifically (no data means the server has never been unavailable)
1667
- if data['unavailable'].is_a? FalseClass
1668
- @unavailable_servers -= 1 if @unavailable_servers
1669
- @unavailable_timeout_time = Time.now
1670
-
1671
- notify_ready if @unavailable_servers.zero?
1672
-
1673
- # Return here so the event doesn't get triggered
1674
- return
1675
- end
1676
-
1677
- event = ServerCreateEvent.new(data, self)
1678
- raise_event(event)
1679
- when :GUILD_UPDATE
1680
- update_guild(data)
1681
-
1682
- event = ServerUpdateEvent.new(data, self)
1683
- raise_event(event)
1684
- when :GUILD_DELETE
1685
- delete_guild(data)
1686
-
1687
- if data['unavailable'].is_a? TrueClass
1688
- LOGGER.warn("Server #{data['id']} is unavailable due to an outage!")
1689
- return # Don't raise an event
1690
- end
1691
-
1692
- event = ServerDeleteEvent.new(data, self)
1693
- raise_event(event)
1694
- when :GUILD_EMOJIS_UPDATE
1695
- server_id = data['guild_id'].to_i
1696
- server = @servers[server_id]
1697
- old_emoji_data = server.emoji.clone
1698
- update_guild_emoji(data)
1699
- new_emoji_data = server.emoji
1700
-
1701
- created_ids = new_emoji_data.keys - old_emoji_data.keys
1702
- deleted_ids = old_emoji_data.keys - new_emoji_data.keys
1703
- updated_ids = old_emoji_data.select do |k, v|
1704
- new_emoji_data[k] && (v.name != new_emoji_data[k].name || v.roles != new_emoji_data[k].roles)
1705
- end.keys
1706
-
1707
- event = ServerEmojiChangeEvent.new(server, data, self)
1708
- raise_event(event)
1709
-
1710
- created_ids.each do |e|
1711
- event = ServerEmojiCreateEvent.new(server, new_emoji_data[e], self)
1712
- raise_event(event)
1713
- end
1714
-
1715
- deleted_ids.each do |e|
1716
- event = ServerEmojiDeleteEvent.new(server, old_emoji_data[e], self)
1717
- raise_event(event)
1718
- end
1719
-
1720
- updated_ids.each do |e|
1721
- event = ServerEmojiUpdateEvent.new(server, old_emoji_data[e], new_emoji_data[e], self)
1722
- raise_event(event)
1723
- end
1724
- when :APPLICATION_COMMAND_PERMISSIONS_UPDATE
1725
- event = ApplicationCommandPermissionsUpdateEvent.new(data, self)
1726
-
1727
- raise_event(event)
1728
- when :INTERACTION_CREATE
1729
- event = InteractionCreateEvent.new(data, self)
1730
- raise_event(event)
1731
-
1732
- case data['type']
1733
- when Interaction::TYPES[:command]
1734
- event = ApplicationCommandEvent.new(data, self)
1735
-
1736
- @event_executor.post do
1737
- Thread.current[:onyxcord_name] = next_event_thread_name('it')
1738
- handler = @application_commands[event.command_name]
1739
- handler&.call(event)
1740
- rescue StandardError => e
1741
- log_exception(e)
1742
- end
1743
- when Interaction::TYPES[:component]
1744
- case data['data']['component_type']
1745
- when Webhooks::View::COMPONENT_TYPES[:button]
1746
- event = ButtonEvent.new(data, self)
1747
-
1748
- raise_event(event)
1749
- when Webhooks::View::COMPONENT_TYPES[:string_select]
1750
- event = StringSelectEvent.new(data, self)
1751
-
1752
- raise_event(event)
1753
- when Webhooks::View::COMPONENT_TYPES[:user_select]
1754
- event = UserSelectEvent.new(data, self)
1755
-
1756
- raise_event(event)
1757
- when Webhooks::View::COMPONENT_TYPES[:role_select]
1758
- event = RoleSelectEvent.new(data, self)
1759
-
1760
- raise_event(event)
1761
- when Webhooks::View::COMPONENT_TYPES[:mentionable_select]
1762
- event = MentionableSelectEvent.new(data, self)
1763
-
1764
- raise_event(event)
1765
- when Webhooks::View::COMPONENT_TYPES[:channel_select]
1766
- event = ChannelSelectEvent.new(data, self)
1767
-
1768
- raise_event(event)
1769
- end
1770
- when Interaction::TYPES[:modal_submit]
1771
-
1772
- event = ModalSubmitEvent.new(data, self)
1773
- raise_event(event)
1774
- when Interaction::TYPES[:autocomplete]
1775
-
1776
- event = AutocompleteEvent.new(data, self)
1777
- raise_event(event)
1778
- end
1779
- when :WEBHOOKS_UPDATE
1780
- event = WebhookUpdateEvent.new(data, self)
1781
- raise_event(event)
1782
- when :THREAD_CREATE
1783
- create_channel(data)
1784
-
1785
- event = ThreadCreateEvent.new(data, self)
1786
- raise_event(event)
1787
- when :THREAD_UPDATE
1788
- update_channel(data)
1789
-
1790
- event = ThreadUpdateEvent.new(data, self)
1791
- raise_event(event)
1792
- when :THREAD_DELETE
1793
- delete_channel(data)
1794
- @thread_members.delete(data['id']&.resolve_id)
1795
-
1796
- # raise ThreadDeleteEvent
1797
- when :THREAD_LIST_SYNC
1798
- server_id = data['guild_id'].to_i
1799
- server = @servers[server_id]
1800
-
1801
- # The `channel_ids` field has two meanings:
1802
- #
1803
- # 1. If the field is not present, the thread list is being synced for the whole server.
1804
- #
1805
- # 2. We are syncing the threads for a specific channel. This can happen when gaining access
1806
- # to a channel.
1807
- if (ids = data['channel_ids']&.map(&:to_i))
1808
- @channels.delete_if { |_, channel| channel.thread? && ids.any?(channel.parent&.id) }
1809
- server&.clear_threads(ids)
1810
- else
1811
- @channels.delete_if { |_, channel| channel.server.id == server_id && channel.thread? }
1812
- server&.clear_threads
1813
- end
1814
-
1815
- data['members'].each { |member| ensure_thread_member(member) }
1816
- data['threads'].each { |channel| ensure_channel(channel) }
1817
-
1818
- # raise ThreadListSyncEvent?
1819
- when :THREAD_MEMBER_UPDATE
1820
- ensure_thread_member(data)
1821
- when :THREAD_MEMBERS_UPDATE
1822
- data['added_members']&.each do |added_member|
1823
- ensure_thread_member(added_member) if added_member['user_id']
1824
- end
1825
-
1826
- data['removed_member_ids']&.each do |member_id|
1827
- @thread_members[data['id']&.resolve_id]&.delete(member_id&.resolve_id)
1828
- end
1829
-
1830
- event = ThreadMembersUpdateEvent.new(data, self)
1831
- raise_event(event)
1832
- when :MESSAGE_POLL_VOTE_ADD
1833
- event = PollVoteAddEvent.new(data, self)
1834
- raise_event(event)
1835
- when :MESSAGE_POLL_VOTE_REMOVE
1836
- event = PollVoteRemoveEvent.new(data, self)
1837
- raise_event(event)
1838
- when :GUILD_SCHEDULED_EVENT_CREATE
1839
- update_guild_scheduled_event(data)
1840
-
1841
- event = ScheduledEventCreateEvent.new(data, self)
1842
- raise_event(event)
1843
- when :GUILD_SCHEDULED_EVENT_UPDATE
1844
- update_guild_scheduled_event(data)
1845
-
1846
- event = ScheduledEventUpdateEvent.new(data, self)
1847
- raise_event(event)
1848
- when :GUILD_SCHEDULED_EVENT_DELETE
1849
- @servers[data['guild_id'].to_i]&.delete_scheduled_event(data['id'].to_i)
1850
-
1851
- event = ScheduledEventDeleteEvent.new(data, self)
1852
- raise_event(event)
1853
- when :GUILD_SCHEDULED_EVENT_USER_ADD
1854
- server = @servers[data['guild_id'].to_i]
1855
- server&.scheduled_event(data['guild_scheduled_event_id'], request: false)&.increment_user_count
1856
-
1857
- event = ScheduledEventUserAddEvent.new(data, self)
1858
- raise_event(event)
1859
- when :GUILD_SCHEDULED_EVENT_USER_REMOVE
1860
- server = @servers[data['guild_id'].to_i]
1861
- server&.scheduled_event(data['guild_scheduled_event_id'], request: false)&.deincrement_user_count
1862
-
1863
- event = ScheduledEventUserRemoveEvent.new(data, self)
1864
- raise_event(event)
1865
- else
1866
- # another event that we don't support yet
1867
- debug "Event #{type} has been received but is unsupported. Raising UnknownEvent"
1868
-
1869
- event = UnknownEvent.new(type, data, self)
1870
- raise_event(event)
1871
- end
1872
-
1873
- # The existence of this array is checked before for performance reasons, since this has to be done for *every*
1874
- # dispatch.
1875
- if @event_handlers && @event_handlers[RawEvent]
1876
- event = RawEvent.new(type, data, self)
1877
- raise_event(event)
1878
- end
1879
- rescue Exception => e
1880
- if defined?(Async::Cancel) && e.is_a?(Async::Cancel)
1881
- LOGGER.debug('Gateway message handling was cancelled.')
1882
- return
1883
- end
1884
-
1885
- LOGGER.error('Gateway message error!')
1886
- log_exception(e)
1887
- end
1888
-
1889
- # Notifies everything there is to be notified that the connection is now ready
1890
- def notify_ready
1891
- if @mode == :raw
1892
- notify_raw_ready
1893
- return
1894
- end
1895
-
1896
- # Make sure to raise the event
1897
- raise_event(ReadyEvent.new(self))
1898
- LOGGER.good 'Ready'
1899
-
1900
- @gateway.notify_ready
1901
- end
1902
-
1903
- def raise_event(event)
1904
- debug("Raised a #{event.class}")
1905
- handle_awaits(event)
1906
-
1907
- @event_handlers ||= {}
1908
- handlers = @event_handlers[event.class]
1909
- return unless handlers
1910
-
1911
- handlers.dup.each do |handler|
1912
- call_event(handler, event) if handler.matches?(event)
1913
- end
1914
- end
1915
-
1916
- def call_event(handler, event)
1917
- @event_executor.post do
1918
- Thread.current[:onyxcord_name] = next_event_thread_name('et')
1919
- begin
1920
- handler.call(event)
1921
- handler.after_call(event)
1922
- rescue StandardError => e
1923
- log_exception(e)
1924
- end
1925
- end
1926
- end
1927
-
1928
- def handle_awaits(event)
1929
- @awaits ||= {}
1930
- @awaits.each_value do |await|
1931
- key, should_delete = await.match(event)
1932
- next unless key
1933
-
1934
- debug("should_delete: #{should_delete}")
1935
- @awaits.delete(await.key) if should_delete
1936
-
1937
- await_event = OnyxCord::Events::AwaitEvent.new(await, event, self)
1938
- raise_event(await_event)
1939
- end
1940
- end
1941
-
1942
- def calculate_intents(intents)
1943
- intents = [intents] unless intents.is_a? Array
1944
-
1945
- intents.reduce(0) do |sum, intent|
1946
- case intent
1947
- when Symbol
1948
- intent = INTENT_ALIASES[intent] || intent
1949
-
1950
- if INTENTS[intent]
1951
- sum | INTENTS[intent]
1952
- else
1953
- LOGGER.warn("Unknown intent: #{intent}")
1954
- sum
1955
- end
1956
- when Integer
1957
- sum | intent
1958
- else
1959
- LOGGER.warn("Invalid intent: #{intent}")
1960
- sum
1961
- end
1962
- end
1963
- end
1964
-
1965
- def dispatch_packet(packet)
1966
- type = packet['t']&.intern
1967
- data = packet['d']
1968
-
1969
- case @mode
1970
- when :raw
1971
- dispatch_raw_packet(packet)
1972
- notify_raw_ready if type == :READY
1973
- when :hybrid
1974
- dispatch_raw_packet(packet)
1975
- handle_dispatch(type, data)
1976
- else
1977
- handle_dispatch(type, data)
1978
- end
1979
- end
1980
-
1981
- def dispatch_raw_packet(packet)
1982
- handlers = @raw_handlers
1983
- return unless handlers
1984
-
1985
- handlers.dup.each do |handler|
1986
- call_raw_handler(handler, packet) if handler.matches?(packet)
1987
- end
1988
- end
1989
-
1990
- def call_raw_handler(handler, packet)
1991
- @event_executor.post do
1992
- Thread.current[:onyxcord_name] = next_event_thread_name('rt')
1993
- handler.call(packet)
1994
- rescue StandardError => e
1995
- log_exception(e)
1996
- end
1997
- end
1998
-
1999
- def notify_raw_ready
2000
- return if @raw_ready_notified
2001
-
2002
- @raw_ready_notified = true
2003
- LOGGER.good 'Ready'
2004
- @gateway.notify_ready
2005
- end
2006
-
2007
- def next_event_thread_name(prefix)
2008
- @current_thread_mutex.synchronize do
2009
- @current_thread += 1
2010
- "#{prefix}-#{@current_thread}"
2011
- end
2012
- end
2013
278
  end
2014
279
  end