onyxcord 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/CONTRIBUTING.md +13 -0
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  8. data/.github/pull_request_template.md +37 -0
  9. data/.github/workflows/ci.yml +78 -0
  10. data/.github/workflows/codeql.yml +65 -0
  11. data/.github/workflows/deploy.yml +54 -0
  12. data/.github/workflows/release.yml +51 -0
  13. data/.gitignore +16 -0
  14. data/.markdownlint.json +4 -0
  15. data/.overcommit.yml +7 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +129 -0
  18. data/.yardopts +1 -0
  19. data/CHANGELOG.md +0 -0
  20. data/Gemfile +7 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +305 -0
  23. data/Rakefile +17 -0
  24. data/bin/console +15 -0
  25. data/bin/setup +7 -0
  26. data/lib/onyxcord/allowed_mentions.rb +43 -0
  27. data/lib/onyxcord/api/application.rb +316 -0
  28. data/lib/onyxcord/api/channel.rb +700 -0
  29. data/lib/onyxcord/api/interaction.rb +67 -0
  30. data/lib/onyxcord/api/invite.rb +44 -0
  31. data/lib/onyxcord/api/server.rb +775 -0
  32. data/lib/onyxcord/api/user.rb +158 -0
  33. data/lib/onyxcord/api/webhook.rb +163 -0
  34. data/lib/onyxcord/api.rb +335 -0
  35. data/lib/onyxcord/await.rb +51 -0
  36. data/lib/onyxcord/bot.rb +1971 -0
  37. data/lib/onyxcord/cache.rb +326 -0
  38. data/lib/onyxcord/colour_rgb.rb +43 -0
  39. data/lib/onyxcord/commands/command_bot.rb +511 -0
  40. data/lib/onyxcord/commands/container.rb +112 -0
  41. data/lib/onyxcord/commands/events.rb +11 -0
  42. data/lib/onyxcord/commands/parser.rb +327 -0
  43. data/lib/onyxcord/commands/rate_limiter.rb +144 -0
  44. data/lib/onyxcord/configuration.rb +125 -0
  45. data/lib/onyxcord/container.rb +988 -0
  46. data/lib/onyxcord/data/activity.rb +271 -0
  47. data/lib/onyxcord/data/application.rb +341 -0
  48. data/lib/onyxcord/data/attachment.rb +91 -0
  49. data/lib/onyxcord/data/audit_logs.rb +438 -0
  50. data/lib/onyxcord/data/avatar_decoration.rb +26 -0
  51. data/lib/onyxcord/data/call.rb +22 -0
  52. data/lib/onyxcord/data/channel.rb +1355 -0
  53. data/lib/onyxcord/data/channel_tag.rb +69 -0
  54. data/lib/onyxcord/data/collectibles.rb +47 -0
  55. data/lib/onyxcord/data/component.rb +583 -0
  56. data/lib/onyxcord/data/embed.rb +258 -0
  57. data/lib/onyxcord/data/emoji.rb +123 -0
  58. data/lib/onyxcord/data/install_params.rb +24 -0
  59. data/lib/onyxcord/data/integration.rb +144 -0
  60. data/lib/onyxcord/data/interaction.rb +1141 -0
  61. data/lib/onyxcord/data/invite.rb +137 -0
  62. data/lib/onyxcord/data/member.rb +528 -0
  63. data/lib/onyxcord/data/message.rb +612 -0
  64. data/lib/onyxcord/data/message_activity.rb +41 -0
  65. data/lib/onyxcord/data/overwrite.rb +109 -0
  66. data/lib/onyxcord/data/poll.rb +365 -0
  67. data/lib/onyxcord/data/primary_server.rb +60 -0
  68. data/lib/onyxcord/data/profile.rb +79 -0
  69. data/lib/onyxcord/data/reaction.rb +64 -0
  70. data/lib/onyxcord/data/recipient.rb +34 -0
  71. data/lib/onyxcord/data/role.rb +449 -0
  72. data/lib/onyxcord/data/role_connection_data.rb +69 -0
  73. data/lib/onyxcord/data/role_subscription.rb +41 -0
  74. data/lib/onyxcord/data/scheduled_event.rb +513 -0
  75. data/lib/onyxcord/data/server.rb +1614 -0
  76. data/lib/onyxcord/data/server_preview.rb +68 -0
  77. data/lib/onyxcord/data/snapshot.rb +112 -0
  78. data/lib/onyxcord/data/team.rb +98 -0
  79. data/lib/onyxcord/data/timestamp.rb +69 -0
  80. data/lib/onyxcord/data/user.rb +324 -0
  81. data/lib/onyxcord/data/voice_region.rb +46 -0
  82. data/lib/onyxcord/data/voice_state.rb +41 -0
  83. data/lib/onyxcord/data/webhook.rb +238 -0
  84. data/lib/onyxcord/data.rb +57 -0
  85. data/lib/onyxcord/errors.rb +246 -0
  86. data/lib/onyxcord/event_executor.rb +80 -0
  87. data/lib/onyxcord/events/await.rb +48 -0
  88. data/lib/onyxcord/events/bans.rb +60 -0
  89. data/lib/onyxcord/events/channels.rb +225 -0
  90. data/lib/onyxcord/events/generic.rb +129 -0
  91. data/lib/onyxcord/events/guilds.rb +269 -0
  92. data/lib/onyxcord/events/integrations.rb +100 -0
  93. data/lib/onyxcord/events/interactions.rb +624 -0
  94. data/lib/onyxcord/events/invites.rb +127 -0
  95. data/lib/onyxcord/events/lifetime.rb +31 -0
  96. data/lib/onyxcord/events/members.rb +110 -0
  97. data/lib/onyxcord/events/message.rb +399 -0
  98. data/lib/onyxcord/events/polls.rb +118 -0
  99. data/lib/onyxcord/events/presence.rb +131 -0
  100. data/lib/onyxcord/events/raw.rb +74 -0
  101. data/lib/onyxcord/events/reactions.rb +218 -0
  102. data/lib/onyxcord/events/roles.rb +87 -0
  103. data/lib/onyxcord/events/scheduled_events.rb +171 -0
  104. data/lib/onyxcord/events/threads.rb +100 -0
  105. data/lib/onyxcord/events/typing.rb +73 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +48 -0
  107. data/lib/onyxcord/events/voice_state_update.rb +106 -0
  108. data/lib/onyxcord/events/webhooks.rb +65 -0
  109. data/lib/onyxcord/gateway.rb +890 -0
  110. data/lib/onyxcord/id_object.rb +39 -0
  111. data/lib/onyxcord/light/data.rb +62 -0
  112. data/lib/onyxcord/light/integrations.rb +73 -0
  113. data/lib/onyxcord/light/light_bot.rb +58 -0
  114. data/lib/onyxcord/light.rb +8 -0
  115. data/lib/onyxcord/logger.rb +120 -0
  116. data/lib/onyxcord/message_components.rb +70 -0
  117. data/lib/onyxcord/paginator.rb +60 -0
  118. data/lib/onyxcord/permissions.rb +255 -0
  119. data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
  120. data/lib/onyxcord/rate_limiter/rest.rb +89 -0
  121. data/lib/onyxcord/version.rb +7 -0
  122. data/lib/onyxcord/voice/encoder.rb +115 -0
  123. data/lib/onyxcord/voice/network.rb +380 -0
  124. data/lib/onyxcord/voice/opcodes.rb +29 -0
  125. data/lib/onyxcord/voice/sodium.rb +157 -0
  126. data/lib/onyxcord/voice/timer.rb +19 -0
  127. data/lib/onyxcord/voice/voice_bot.rb +386 -0
  128. data/lib/onyxcord/webhooks.rb +14 -0
  129. data/lib/onyxcord/websocket.rb +62 -0
  130. data/lib/onyxcord.rb +180 -0
  131. data/onyxcord-webhooks.gemspec +30 -0
  132. data/onyxcord.gemspec +50 -0
  133. metadata +421 -0
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'onyxcord/api'
4
+ require 'onyxcord/api/server'
5
+ require 'onyxcord/api/invite'
6
+ require 'onyxcord/api/user'
7
+ require 'onyxcord/configuration'
8
+ require 'onyxcord/data'
9
+
10
+ module OnyxCord
11
+ # This mixin module does caching stuff for the library. It conveniently separates the logic behind
12
+ # the caching (like, storing the user hashes or making API calls to retrieve things) from the Bot that
13
+ # actually uses it.
14
+ module Cache
15
+ # Initializes this cache
16
+ def init_cache
17
+ @cache_policy ||= OnyxCord.configuration.normalize_cache(:full)
18
+
19
+ @users = cache_enabled?(:users) ? {} : nil
20
+
21
+ @voice_regions = cache_enabled?(:voice_regions) ? {} : nil
22
+
23
+ @servers = cache_enabled?(:servers) ? {} : nil
24
+
25
+ @channels = cache_enabled?(:channels) ? {} : nil
26
+ @pm_channels = cache_enabled?(:pm_channels) ? {} : nil
27
+ @thread_members = cache_enabled?(:thread_members) ? {} : nil
28
+ @server_previews = cache_enabled?(:server_previews) ? {} : nil
29
+ end
30
+
31
+ def cache_enabled?(key)
32
+ @cache_policy.fetch(key, true)
33
+ end
34
+
35
+ # Returns or caches the available voice regions
36
+ def voice_regions
37
+ return fetch_voice_regions unless cache_enabled?(:voice_regions)
38
+
39
+ @voice_regions ||= {}
40
+ return @voice_regions unless @voice_regions.empty?
41
+
42
+ @voice_regions = fetch_voice_regions
43
+ end
44
+
45
+ def fetch_voice_regions
46
+ regions_by_id = {}
47
+
48
+ regions = JSON.parse API.voice_regions(token)
49
+ regions.each do |data|
50
+ regions_by_id[data['id']] = VoiceRegion.new(data)
51
+ end
52
+
53
+ regions_by_id
54
+ end
55
+
56
+ # Gets a channel given its ID. This queries the internal channel cache, and if the channel doesn't
57
+ # exist in there, it will get the data from Discord.
58
+ # @param id [Integer] The channel ID for which to search for.
59
+ # @param server [Server] The server for which to search the channel for. If this isn't specified, it will be
60
+ # inferred using the API
61
+ # @return [Channel, nil] The channel identified by the ID.
62
+ # @raise OnyxCord::Errors::NoPermission
63
+ def channel(id, server = nil)
64
+ id = id.resolve_id
65
+
66
+ debug("Obtaining data for channel with id #{id}")
67
+ return @channels[id] if @channels&.[](id)
68
+
69
+ begin
70
+ response = API::Channel.resolve(token, id)
71
+ rescue OnyxCord::Errors::UnknownChannel
72
+ return nil
73
+ end
74
+ channel = Channel.new(JSON.parse(response), self, server)
75
+ @channels[id] = channel if cache_enabled?(:channels)
76
+ channel
77
+ end
78
+
79
+ alias_method :group_channel, :channel
80
+
81
+ # Gets a user by its ID.
82
+ # @note This can only resolve users known by the bot (i.e. that share a server with the bot).
83
+ # @param id [Integer] The user ID that should be resolved.
84
+ # @return [User, nil] The user identified by the ID, or `nil` if it couldn't be found.
85
+ def user(id)
86
+ id = id.resolve_id
87
+ return @users[id] if @users&.[](id)
88
+
89
+ LOGGER.out("Resolving user #{id}")
90
+ begin
91
+ response = API::User.resolve(token, id)
92
+ rescue OnyxCord::Errors::UnknownUser
93
+ return nil
94
+ end
95
+ user = User.new(JSON.parse(response), self)
96
+ @users[id] = user if cache_enabled?(:users)
97
+ user
98
+ end
99
+
100
+ # Gets a server by its ID.
101
+ # @note This can only resolve servers the bot is currently in.
102
+ # @param id [Integer] The server ID that should be resolved.
103
+ # @return [Server, nil] The server identified by the ID, or `nil` if it couldn't be found.
104
+ def server(id)
105
+ id = id.resolve_id
106
+ return @servers[id] if @servers&.[](id)
107
+
108
+ LOGGER.out("Resolving server #{id}")
109
+ begin
110
+ response = API::Server.resolve(token, id)
111
+ rescue OnyxCord::Errors::NoPermission
112
+ return nil
113
+ end
114
+ server = Server.new(JSON.parse(response), self)
115
+ @servers[id] = server if cache_enabled?(:servers)
116
+ server
117
+ end
118
+
119
+ # Gets a member by both IDs, or `Server` and user ID.
120
+ # @param server_or_id [Server, Integer] The `Server` or server ID for which a member should be resolved
121
+ # @param user_id [Integer] The ID of the user that should be resolved
122
+ # @return [Member, nil] The member identified by the IDs, or `nil` if none could be found
123
+ def member(server_or_id, user_id)
124
+ server_id = server_or_id.resolve_id
125
+ user_id = user_id.resolve_id
126
+ server = server_or_id.is_a?(Server) ? server_or_id : self.server(server_id)
127
+
128
+ return server.member(user_id) if server.member_cached?(user_id)
129
+
130
+ LOGGER.out("Resolving member #{server_id} on server #{user_id}")
131
+ begin
132
+ response = API::Server.resolve_member(token, server_id, user_id)
133
+ rescue OnyxCord::Errors::UnknownUser, OnyxCord::Errors::UnknownMember
134
+ return nil
135
+ end
136
+ member = Member.new(JSON.parse(response), server, self)
137
+ server.cache_member(member)
138
+ end
139
+
140
+ # Creates a PM channel for the given user ID, or if one exists already, returns that one.
141
+ # It is recommended that you use {User#pm} instead, as this is mainly for internal use. However,
142
+ # usage of this method may be unavoidable if only the user ID is known.
143
+ # @param id [Integer] The user ID to generate a private channel for.
144
+ # @return [Channel] A private channel for that user.
145
+ def pm_channel(id)
146
+ id = id.resolve_id
147
+ return @pm_channels[id] if @pm_channels&.[](id)
148
+
149
+ debug("Creating pm channel with user id #{id}")
150
+ response = API::User.create_pm(token, id)
151
+ channel = Channel.new(JSON.parse(response), self)
152
+ @pm_channels[id] = channel if cache_enabled?(:pm_channels)
153
+ channel
154
+ end
155
+
156
+ alias_method :private_channel, :pm_channel
157
+
158
+ # Get a server preview. If the bot isn't a member of the server, the server must be discoverable.
159
+ # @param id [Integer, String, Server] the ID of the server preview to get.
160
+ # @return [ServerPreview, nil] the server preview, or `nil` if the server isn't accessible.
161
+ def server_preview(id)
162
+ id = id.resolve_id
163
+ return @server_previews[id] if @server_previews&.[](id)
164
+
165
+ response = JSON.parse(API::Server.preview(token, id))
166
+ preview = ServerPreview.new(response, self)
167
+ @server_previews[id] = preview if cache_enabled?(:server_previews)
168
+ preview
169
+ rescue StandardError
170
+ nil
171
+ end
172
+
173
+ # Ensures a given user object is cached and if not, cache it from the given data hash.
174
+ # @param data [Hash] A data hash representing a user.
175
+ # @return [User] the user represented by the data hash.
176
+ def ensure_user(data)
177
+ return User.new(data, self) unless cache_enabled?(:users)
178
+
179
+ @users ||= {}
180
+ if @users.include?(data['id'].to_i)
181
+ @users[data['id'].to_i]
182
+ else
183
+ @users[data['id'].to_i] = User.new(data, self)
184
+ end
185
+ end
186
+
187
+ # Ensures a given server object is cached and if not, cache it from the given data hash.
188
+ # @param data [Hash] A data hash representing a server.
189
+ # @param force_cache [true, false] Whether the object in cache should be updated with the given
190
+ # data if it already exists.
191
+ # @return [Server] the server represented by the data hash.
192
+ def ensure_server(data, force_cache = false)
193
+ return Server.new(data, self) unless cache_enabled?(:servers)
194
+
195
+ @servers ||= {}
196
+ if @servers.include?(data['id'].to_i)
197
+ server = @servers[data['id'].to_i]
198
+ server.update_data(data) if force_cache
199
+ server
200
+ else
201
+ @servers[data['id'].to_i] = Server.new(data, self)
202
+ end
203
+ end
204
+
205
+ # Ensures a given channel object is cached and if not, cache it from the given data hash.
206
+ # @param data [Hash] A data hash representing a channel.
207
+ # @param server [Server, nil] The server the channel is on, if known.
208
+ # @return [Channel] the channel represented by the data hash.
209
+ def ensure_channel(data, server = nil)
210
+ return Channel.new(data, self, server) unless cache_enabled?(:channels)
211
+
212
+ @channels ||= {}
213
+ if @channels.include?(data['id'].to_i)
214
+ @channels[data['id'].to_i]
215
+ else
216
+ @channels[data['id'].to_i] = Channel.new(data, self, server)
217
+ end
218
+ end
219
+
220
+ # Ensures a given thread member object is cached.
221
+ # @param data [Hash] Thread member data.
222
+ def ensure_thread_member(data)
223
+ return unless cache_enabled?(:thread_members)
224
+
225
+ thread_id = data['id'].to_i
226
+ user_id = data['user_id'].to_i
227
+
228
+ @thread_members ||= {}
229
+ @thread_members[thread_id] ||= {}
230
+ @thread_members[thread_id][user_id] = data.slice('join_timestamp', 'flags')
231
+ end
232
+
233
+ # Requests member chunks for a given server ID.
234
+ # @param id [Integer] The server ID to request chunks for.
235
+ def request_chunks(id)
236
+ id = id.resolve_id
237
+
238
+ bucket = (@request_members_rl[id] ||= { mutex: Mutex.new, time: Time.at(0) })
239
+
240
+ bucket[:mutex].synchronize do
241
+ last = bucket[:time]
242
+ now = Time.now
243
+
244
+ if now < last
245
+ duration = last - now
246
+
247
+ LOGGER.info("Preemptively locking REQUEST_GUILD_MEMBERS for #{duration} seconds")
248
+ sleep(duration)
249
+ end
250
+
251
+ @gateway.send_request_members(id, '', 0)
252
+ bucket[:time] = (Time.now + 30)
253
+ end
254
+ end
255
+
256
+ # Gets the code for an invite.
257
+ # @param invite [String, Invite] The invite to get the code for. Possible formats are:
258
+ #
259
+ # * An {Invite} object
260
+ # * The code for an invite
261
+ # * A fully qualified invite URL (e.g. `https://discord.com/invite/0A37aN7fasF7n83q`)
262
+ # * A short invite URL with protocol (e.g. `https://discord.gg/0A37aN7fasF7n83q`)
263
+ # * A short invite URL without protocol (e.g. `discord.gg/0A37aN7fasF7n83q`)
264
+ # @return [String] Only the code for the invite.
265
+ def resolve_invite_code(invite)
266
+ invite = invite.code if invite.is_a? OnyxCord::Invite
267
+ invite = invite[(invite.rindex('/') + 1)..] if invite.start_with?('http', 'discord.gg')
268
+ invite
269
+ end
270
+
271
+ # Gets information about an invite.
272
+ # @param invite [String, Invite] The invite to join. For possible formats see {#resolve_invite_code}.
273
+ # @return [Invite] The invite with information about the given invite URL.
274
+ def invite(invite)
275
+ code = resolve_invite_code(invite)
276
+ Invite.new(JSON.parse(API::Invite.resolve(token, code)), self)
277
+ end
278
+
279
+ # Finds a channel given its name and optionally the name of the server it is in.
280
+ # @param channel_name [String] The channel to search for.
281
+ # @param server_name [String] The server to search for, or `nil` if only the channel should be searched for.
282
+ # @param type [Integer, nil] The type of channel to search for (0: text, 1: private, 2: voice, 3: group), or `nil` if any type of
283
+ # channel should be searched for
284
+ # @return [Array<Channel>] The array of channels that were found. May be empty if none were found.
285
+ def find_channel(channel_name, server_name = nil, type: nil)
286
+ results = []
287
+
288
+ if /<#(?<id>\d+)>?/ =~ channel_name
289
+ # Check for channel mentions separately
290
+ return [channel(id)]
291
+ end
292
+
293
+ @servers.each_value do |server|
294
+ server.channels.each do |channel|
295
+ results << channel if channel.name == channel_name && (server_name || server.name) == server.name && (!type || (channel.type == type))
296
+ end
297
+ end
298
+
299
+ results
300
+ end
301
+
302
+ # Finds a user given its username or username & discriminator.
303
+ # @overload find_user(username)
304
+ # Find all cached users with a certain username.
305
+ # @param username [String] The username to look for.
306
+ # @return [Array<User>] The array of users that were found. May be empty if none were found.
307
+ # @overload find_user(username, discrim)
308
+ # Find a cached user with a certain username and discriminator.
309
+ # Find a user by name and discriminator
310
+ # @param username [String] The username to look for.
311
+ # @param discrim [String] The user's discriminator
312
+ # @return [User, nil] The user that was found, or `nil` if none was found
313
+ # @note This method only searches through users that have been cached. Users that have not yet been cached
314
+ # by the bot but still share a connection with the user (mutual server) will not be found.
315
+ # @example Find users by name
316
+ # bot.find_user('z64') #=> Array<User>
317
+ # @example Find a user by name and discriminator
318
+ # bot.find_user('z64', '2639') #=> User
319
+ def find_user(username, discrim = nil)
320
+ users = @users.values.find_all { |e| e.username == username }
321
+ return users.find { |u| u.discrim == discrim } if discrim
322
+
323
+ users
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord
4
+ # A colour (red, green and blue values). Used for role colours. If you prefer the American spelling, the alias
5
+ # {ColorRGB} is also available.
6
+ class ColourRGB
7
+ # @return [Integer] the red part of this colour (0-255).
8
+ attr_reader :red
9
+
10
+ # @return [Integer] the green part of this colour (0-255).
11
+ attr_reader :green
12
+
13
+ # @return [Integer] the blue part of this colour (0-255).
14
+ attr_reader :blue
15
+
16
+ # @return [Integer] the colour's RGB values combined into one integer.
17
+ attr_reader :combined
18
+ alias_method :to_i, :combined
19
+
20
+ # Make a new colour from the combined value.
21
+ # @param combined [String, Integer] The colour's RGB values combined into one integer or a hexadecimal string
22
+ # @example Initialize a with a base 10 integer
23
+ # ColourRGB.new(7506394) #=> ColourRGB
24
+ # ColourRGB.new(0x7289da) #=> ColourRGB
25
+ # @example Initialize a with a hexadecimal string
26
+ # ColourRGB.new('7289da') #=> ColourRGB
27
+ def initialize(combined)
28
+ @combined = combined.is_a?(String) ? combined.to_i(16) : combined
29
+ @red = (@combined >> 16) & 0xFF
30
+ @green = (@combined >> 8) & 0xFF
31
+ @blue = @combined & 0xFF
32
+ end
33
+
34
+ # @return [String] the colour as a hexadecimal.
35
+ def hex
36
+ @combined.to_s(16)
37
+ end
38
+ alias_method :hexadecimal, :hex
39
+ end
40
+
41
+ # Alias for the class {ColourRGB}
42
+ ColorRGB = ColourRGB
43
+ end