disrb 0.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.
data/lib/disrb.rb ADDED
@@ -0,0 +1,637 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'async'
6
+ require 'async/http/endpoint'
7
+ require 'async/websocket/client'
8
+ require 'faraday'
9
+ require 'disrb/guild'
10
+ require 'disrb/logger'
11
+ require 'disrb/user'
12
+ require 'disrb/message'
13
+
14
+ # TODO: If there is more than 1 optional parameter in a function, I should change it from setting the default value via
15
+ # = to : so that the user can pass only the parameters they want to set
16
+
17
+ # DiscordApi
18
+ # The class that contains everything that interacts with the Discord API.
19
+ class DiscordApi
20
+ attr_accessor(:base_url, :authorization_header, :application_id, :interaction_created, :interaction, :logger)
21
+
22
+ def initialize(authorization_token_type, authorization_token, verbosity_level = nil)
23
+ @api_version = '10'
24
+ @base_url = "https://discord.com/api/v#{@api_version}"
25
+ @authorization_token_type = authorization_token_type
26
+ @authorization_token = authorization_token
27
+ @authorization_header = "#{authorization_token_type} #{authorization_token}"
28
+ url = URI("#{@base_url}/applications/@me")
29
+ headers = { 'Authorization': @authorization_header }
30
+ @application_id = JSON.parse(Net::HTTP.get(url, headers))['id']
31
+ @interaction_created = false
32
+ @interaction = {}
33
+ if verbosity_level.nil?
34
+ @verbosity_level = 3
35
+ elsif verbosity_level.is_a?(String)
36
+ case verbosity_level.downcase
37
+ when 'all'
38
+ @verbosity_level = 4
39
+ when 'info'
40
+ @verbosity_level = 3
41
+ when 'warning'
42
+ @verbosity_level = 2
43
+ when 'error'
44
+ @verbosity_level = 1
45
+ when 'none'
46
+ @verbosity_level = 0
47
+ else
48
+ Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
49
+ @verbosity_level = 3
50
+ end
51
+ elsif verbosity_level.is_a?(Integer)
52
+ if verbosity_level >= 0 && verbosity_level <= 4
53
+ @verbosity_level = verbosity_level
54
+ else
55
+ Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
56
+ @verbosity_level = 3
57
+ end
58
+ else
59
+ Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
60
+ @verbosity_level = 3
61
+ end
62
+ @logger = Logger2.new(@verbosity_level)
63
+ end
64
+
65
+ def self.handle_query_strings(query_string_hash)
66
+ query_string_array = []
67
+ query_string_hash.each do |key, value|
68
+ if value.nil?
69
+ next
70
+ elsif query_string_array.empty?
71
+ query_string_array << "?#{key}=#{value}"
72
+ else
73
+ query_string_array << "&#{key}=#{value}"
74
+ end
75
+ end
76
+ query_string_array << '' if query_string_array.empty?
77
+ query_string_array.join
78
+ end
79
+
80
+ def self.handle_snowflake(snowflake)
81
+ snowflake = snowflake.to_s(2).rjust(64, '0')
82
+ {
83
+ discord_epoch_timestamp: snowflake[0..41],
84
+ internal_worker_id: snowflake[42..46],
85
+ internal_process_id: snowflake[47..51],
86
+ gen_id_on_process: snowflake[52..64],
87
+ unix_timestamp: snowflake[0..41].to_i(2) + 1_420_070_400_000,
88
+ timestamp: Time.at((snowflake[0..41].to_i(2) + 1_420_070_400_000) / 1000).utc
89
+ }
90
+ end
91
+
92
+ def create_guild_application_command(guild_id, name, name_localizations: nil, description: nil,
93
+ description_localizations: nil, options: nil, default_member_permissions: nil,
94
+ default_permission: true, type: 1, nsfw: false)
95
+ output = {}
96
+ output[:name] = name
97
+ output[:name_localizations] = name_localizations unless name_localizations.nil?
98
+ output[:description] = description unless description.nil?
99
+ output[:description_localizations] = description_localizations unless description_localizations.nil?
100
+ output[:options] = options unless options.nil?
101
+ output[:default_permission] = default_permission
102
+ output[:type] = type
103
+ output[:nsfw] = nsfw
104
+ output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
105
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands")
106
+ data = JSON.generate(output)
107
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
108
+ Net::HTTP.post(url, data, headers)
109
+ end
110
+
111
+ def create_guild_application_commands(application_commands_array)
112
+ if application_commands_array.is_a?(Array)
113
+ application_commands_array.each do |parameter_array|
114
+ if parameter_array.is_a?(Array)
115
+ create_guild_application_command(*parameter_array)
116
+ else
117
+ @logger.error("Invalid parameter array: #{parameter_array}. Expected an array of parameters.")
118
+ end
119
+ end
120
+ else
121
+ @logger.error("Invalid application commands array: #{application_commands_array}. Expected an array of arrays.")
122
+ end
123
+ end
124
+
125
+ def create_global_application_command(name, name_localizations: nil, description: nil,
126
+ description_localizations: nil, options: nil,
127
+ default_member_permissions: nil, default_permission: true,
128
+ integration_types: nil, contexts: nil, type: 1, nsfw: false)
129
+ output = {}
130
+ output[:name] = name
131
+ output[:name_localizations] = name_localizations unless name_localizations.nil?
132
+ output[:description] = description unless description.nil?
133
+ output[:description_localizations] = description_localizations unless description_localizations.nil?
134
+ output[:options] = options unless options.nil?
135
+ output[:default_permission] = default_permission
136
+ output[:type] = type
137
+ output[:nsfw] = nsfw
138
+ output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
139
+ output[:integration_types] = integration_types unless integration_types.nil?
140
+ output[:contexts] = contexts unless contexts.nil?
141
+ url = URI("#{@base_url}/applications/#{@application_id}/commands")
142
+ data = JSON.generate(output)
143
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
144
+ Net::HTTP.post(url, data, headers)
145
+ end
146
+
147
+ def create_global_application_commands(application_commands_array)
148
+ if application_commands_array.is_a?(Array)
149
+ application_commands_array.each do |parameter_array|
150
+ if parameter_array.is_a?(Array)
151
+ create_global_application_command(*parameter_array)
152
+ else
153
+ @logger.error("Invalid parameter array: #{parameter_array}. Expected an array of parameters.")
154
+ end
155
+ end
156
+ else
157
+ @logger.error("Invalid application commands array: #{application_commands_array}. Expected an array of arrays.")
158
+ end
159
+ end
160
+
161
+ def edit_global_application_command(command_id, name = nil, name_localizations = nil, description = nil,
162
+ description_localizations = nil, options = nil, default_member_permissions = nil,
163
+ default_permission: true, integration_types: nil, contexts: nil, nsfw: nil)
164
+ output = {}
165
+ output[:name] = name
166
+ output[:name_localizations] = name_localizations unless name_localizations.nil?
167
+ output[:description] = description unless description.nil?
168
+ output[:description_localizations] = description_localizations unless description_localizations.nil?
169
+ output[:options] = options unless options.nil?
170
+ output[:default_permission] = default_permission
171
+ output[:nsfw] = nsfw
172
+ output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
173
+ output[:integration_types] = integration_types unless integration_types.nil?
174
+ output[:contexts] = contexts unless contexts.nil?
175
+ url = URI("#{@base_url}/applications/#{@application_id}/commands/#{command_id}")
176
+ data = JSON.generate(output)
177
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
178
+ Net::HTTP.patch(url, data, headers)
179
+ end
180
+
181
+ def edit_guild_application_command(guild_id, command_id, name = nil, name_localizations = nil, description = nil,
182
+ description_localizations = nil, options = nil, default_member_permissions = nil,
183
+ default_permission: true, nsfw: nil)
184
+ output = {}
185
+ output[:name] = name
186
+ output[:name_localizations] = name_localizations unless name_localizations.nil?
187
+ output[:description] = description unless description.nil?
188
+ output[:description_localizations] = description_localizations unless description_localizations.nil?
189
+ output[:options] = options unless options.nil?
190
+ output[:default_permission] = default_permission
191
+ output[:nsfw] = nsfw
192
+ output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
193
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}")
194
+ data = JSON.generate(output)
195
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
196
+ Net::HTTP.patch(url, data, headers)
197
+ end
198
+
199
+ def delete_global_application_command(command_id)
200
+ url = URI("#{@base_url}/applications/#{@application_id}/commands/#{command_id}")
201
+ headers = { 'Authorization': @authorization_header }
202
+ Net::HTTP.delete(url, headers)
203
+ end
204
+
205
+ def delete_guild_application_command(guild_id, command_id)
206
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}")
207
+ headers = { 'Authorization': @authorization_header }
208
+ Net::HTTP.delete(url, headers)
209
+ end
210
+
211
+ def get_guild_application_commands(guild_id, with_localizations: false)
212
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands?with_localizations=" \
213
+ "#{with_localizations}")
214
+ headers = { 'Authorization': @authorization_header }
215
+ Net::HTTP.get(url, headers)
216
+ end
217
+
218
+ def get_global_application_commands(with_localizations: false)
219
+ url = URI("#{@base_url}/applications/#{@application_id}/commands?with_localizations=#{with_localizations}")
220
+ headers = { 'Authorization': @authorization_header }
221
+ Net::HTTP.get(url, headers)
222
+ end
223
+
224
+ def get_global_application_command(command_id)
225
+ url = URI("#{@base_url}/applications/#{@application_id}/commands/#{command_id}")
226
+ headers = { 'Authorization': @authorization_header }
227
+ Net::HTTP.get(url, headers)
228
+ end
229
+
230
+ def get_guild_application_command(guild_id, command_id)
231
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}")
232
+ headers = { 'Authorization': @authorization_header }
233
+ Net::HTTP.get(url, headers)
234
+ end
235
+
236
+ def bulk_overwrite_global_application_commands(commands)
237
+ url = URI("#{@base_url}/applications/#{@application_id}/commands")
238
+ data = JSON.generate(commands)
239
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
240
+ Net::HTTP.put(url, data, headers)
241
+ end
242
+
243
+ def bulk_overwrite_guild_application_commands(guild_id, commands)
244
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands")
245
+ data = JSON.generate(commands)
246
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
247
+ Net::HTTP.put(url, data, headers)
248
+ end
249
+
250
+ def get_guild_application_command_permissions(guild_id)
251
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/permissions")
252
+ headers = { 'Authorization': @authorization_header }
253
+ Net::HTTP.get(url, headers)
254
+ end
255
+
256
+ def get_application_command_permissions(guild_id, command_id)
257
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}/permissions")
258
+ headers = { 'Authorization': @authorization_header }
259
+ Net::HTTP.get(url, headers)
260
+ end
261
+
262
+ def edit_application_command_permissions(guild_id, command_id, permissions)
263
+ url = URI("#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}/permissions")
264
+ data = JSON.generate(permissions)
265
+ headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
266
+ Net::HTTP.put(url, data, headers)
267
+ end
268
+
269
+ def connect_gateway(activities: nil, os: nil, browser: nil, device: nil, intents: nil, presence_since: nil,
270
+ presence_status: nil, presence_afk: nil, &block)
271
+ if @authorization_token_type == 'Bearer'
272
+ acceptable_activities_keys = %w[name type url created_at timestamps application_id details state emoji party
273
+ assets secrets instance flags buttons]
274
+ elsif @authorization_token_type == 'Bot'
275
+ acceptable_activities_keys = %w[name state type url]
276
+ end
277
+ if activities.is_a?(Hash)
278
+ activities.each_key do |key|
279
+ next if acceptable_activities_keys.include?(key.to_s)
280
+
281
+ @logger.error("Unknown activity key: #{key}. Deleting key from hash.")
282
+ activities.delete(key)
283
+ end
284
+ if activities.empty?
285
+ @logger.error('Empty activity hash. No activities will be sent.')
286
+ activities = nil
287
+ else
288
+ activities = [activities]
289
+ end
290
+ elsif activities.is_a?(Array)
291
+ activities.each do |activity|
292
+ if activity.is_a?(Hash)
293
+ activity.each_key do |key|
294
+ next if acceptable_activities_keys.include?(key.to_s)
295
+
296
+ @logger.error("Unknown activity key: #{key}. Deleting key from Hash.")
297
+ activity.delete(key)
298
+ end
299
+ if activity.empty?
300
+ @logger.error('Empty activity hash. Deleting from Array.')
301
+ activities.delete(activity)
302
+ end
303
+ else
304
+ @logger.error("Invalid activity: #{activity}. Expected a Hash. Deleting from Array.")
305
+ activities.delete(activity)
306
+ end
307
+ end
308
+ if activities.empty?
309
+ @logger.error('Empty activities Array. No activities will be sent.')
310
+ activities = nil
311
+ end
312
+ elsif !activities.nil?
313
+ @logger.error("Invalid activities: #{activities}. Expected a Hash or an Array of Hashes.")
314
+ activities = nil
315
+ end
316
+ unless os.is_a?(String) || os.nil?
317
+ @logger.error("Invalid OS: #{os}. Expected a String. Defaulting to #{RbConfig::CONFIG['host_os']}.")
318
+ os = nil
319
+ end
320
+ unless browser.is_a?(String) || browser.nil?
321
+ @logger.error("Invalid browser: #{browser}. Expected a String. Defaulting to 'discord.rb'.")
322
+ browser = nil
323
+ end
324
+ unless device.is_a?(String) || device.nil?
325
+ @logger.error("Invalid device: #{device}. Expected a String. Defaulting to 'discord.rb'.")
326
+ device = nil
327
+ end
328
+ unless (intents.is_a?(Integer) && intents >= 0 && intents <= 131_071) || intents.nil?
329
+ @logger.error("Invalid intents: #{intents}. Expected an Integer between 0 and 131.071. Defaulting to 513" \
330
+ ' (GUILD_MESSAGES, GUILDS).')
331
+ intents = nil
332
+ end
333
+ unless presence_since.is_a?(Integer) || presence_since == true || presence_since.nil?
334
+ @logger.error("Invalid presence since: #{presence_since}. Expected an Integer or true. Defaulting to nil.")
335
+ presence_since = nil
336
+ end
337
+ unless presence_status.is_a?(String) || presence_status.nil?
338
+ @logger.error("Invalid presence status: #{presence_status}. Expected a String. Defaulting to nil.")
339
+ presence_status = nil
340
+ end
341
+ unless [true, false].include?(presence_afk) || presence_afk.nil?
342
+ @logger.error("Invalid presence afk: #{presence_afk}. Expected a Boolean. Defaulting to nil.")
343
+ presence_afk = nil
344
+ end
345
+ Async do |_task|
346
+ rescue_connection, sequence, resume_gateway_url, session_id = nil
347
+ loop do
348
+ url = if rescue_connection.nil?
349
+ "#{JSON.parse(Net::HTTP.get(URI("#{@base_url}/gateway")))['url']}/?v=#{@api_version}&encoding=json"
350
+ else
351
+ "#{rescue_connection[:resume_gateway_url]}/?v=#{@api_version}&encoding=json"
352
+ end
353
+ endpoint = Async::HTTP::Endpoint.parse(url, alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
354
+ Async::WebSocket::Client.connect(endpoint) do |connection|
355
+ connection.write JSON.generate({ op: 1, d: nil })
356
+ connection.flush
357
+
358
+ if rescue_connection.nil?
359
+ identify = {}
360
+ identify[:op] = 2
361
+ identify[:d] = {}
362
+ identify[:d][:token] = @authorization_header
363
+ identify[:d][:intents] = intents || 513
364
+ identify[:d][:properties] = {}
365
+ identify[:d][:properties][:os] = os || RbConfig::CONFIG['host_os']
366
+ identify[:d][:properties][:browser] = browser || 'discord.rb'
367
+ identify[:d][:properties][:device] = device || 'discord.rb'
368
+ if !activities.nil? || !presence_since.nil? || !presence_status.nil? || !presence_afk.nil?
369
+ identify[:d][:presence] = {}
370
+ identify[:d][:presence][:activities] = activities unless activities.nil?
371
+ if presence_since == true
372
+ identify[:d][:presence][:since] = (Time.now.to_f * 1000).floor
373
+ elsif presence_since.is_a?(Integer)
374
+ identify[:d][:presence][:since] = presence_since
375
+ end
376
+ identify[:d][:presence][:status] = presence_status unless presence_status.nil?
377
+ identify[:d][:presence][:afk] = presence_afk unless presence_afk.nil?
378
+ end
379
+ @logger.debug("Identify payload: #{JSON.generate(identify)}")
380
+ connection.write(JSON.generate(identify))
381
+ else
382
+ @logger.info('Resuming connection...')
383
+ resume = {}
384
+ resume[:op] = 6
385
+ resume[:d] = {}
386
+ resume[:d][:token] = @authorization_header
387
+ resume[:d][:session_id] = rescue_connection[:session_id]
388
+ resume[:d][:seq] = rescue_connection[:sequence]
389
+ @logger.debug("Resume payload: #{JSON.generate(resume)}")
390
+ connection.write(JSON.generate(resume))
391
+ rescue_connection, sequence, resume_gateway_url, session_id = nil
392
+ end
393
+ connection.flush
394
+
395
+ loop do
396
+ message = connection.read
397
+ message = JSON.parse(message, symbolize_names: true)
398
+ @logger.debug(message)
399
+
400
+ case message
401
+ in { op: 10 }
402
+ @logger.info('Received Hello')
403
+ @heartbeat_interval = message[:d][:heartbeat_interval]
404
+ in { op: 1 }
405
+ @logger.info('Received Heartbeat Request')
406
+ connection.write JSON.generate({ op: 1, d: nil })
407
+ connection.flush
408
+ in { op: 11 }
409
+ @logger.info('Received Heartbeat ACK')
410
+ in { op: 0, t: 'INTERACTION_CREATE' }
411
+ @logger.info('An interaction was created')
412
+ sequence = message[:s]
413
+ block.call(message)
414
+ in { op: 0, t: 'READY' }
415
+ @logger.info('Recieved Ready')
416
+ session_id = message[:d][:session_id]
417
+ resume_gateway_url = message[:d][:resume_gateway_url]
418
+ sequence = message[:s]
419
+ in { op: 0 }
420
+ @logger.info('An event was dispatched')
421
+ sequence = message[:s]
422
+ in { op: 7 }
423
+ rescue_connection = { type: 'reconnect', session_id: session_id, resume_gateway_url: resume_gateway_url,
424
+ sequence: sequence }
425
+ @logger.warn('Received Reconnect. A rescue will be attempted....')
426
+ else
427
+ @logger.error('Unimplemented event type')
428
+ end
429
+ end
430
+ end
431
+ rescue Protocol::WebSocket::ClosedError
432
+ @logger.warn('WebSocket connection closed. Attempting rescue.')
433
+ if rescue_connection && rescue_connection[:type] == 'reconnect'
434
+ @logger.info('Rescue possible. Starting...')
435
+ next
436
+ end
437
+ end
438
+ end
439
+ end
440
+
441
+ def respond_interaction(interaction, response, with_response: false)
442
+ query_string_hash = {}
443
+ query_string_hash[:with_response] = with_response
444
+ query_string = DiscordApi.handle_query_strings(query_string_hash)
445
+ url = "#{@base_url}/interactions/#{interaction[:d][:id]}/#{interaction[:d][:token]}/callback#{query_string}"
446
+ data = JSON.generate(response)
447
+ headers = { 'content-type': 'application/json' }
448
+ response = DiscordApi.post(url, data, headers)
449
+ return response if (response.status == 204 && !with_response) || (response.status == 200 && with_response)
450
+
451
+ @logger.error("Failed to respond to interaction. Response: #{response.body}")
452
+ response
453
+ end
454
+
455
+ def self.calculate_permissions_integer(permissions)
456
+ bitwise_permission_flags = {
457
+ create_instant_invite: 0x0000000000000001,
458
+ kick_members: 0x0000000000000002,
459
+ ban_members: 0x0000000000000004,
460
+ administrator: 0x0000000000000008,
461
+ manage_channels: 0x0000000000000010,
462
+ manage_guild: 0x0000000000000020,
463
+ add_reactions: 0x0000000000000040,
464
+ view_audit_log: 0x0000000000000080,
465
+ priority_speaker: 0x0000000000000100,
466
+ stream: 0x0000000000000200,
467
+ view_channel: 0x0000000000000400,
468
+ send_messages: 0x0000000000000800,
469
+ send_tts_messages: 0x0000000000001000,
470
+ manage_messages: 0x0000000000002000,
471
+ embed_links: 0x0000000000004000,
472
+ attach_files: 0x0000000000008000,
473
+ read_message_history: 0x0000000000010000,
474
+ mention_everyone: 0x0000000000020000,
475
+ use_external_emojis: 0x0000000000040000,
476
+ view_guild_insights: 0x0000000000080000,
477
+ connect: 0x0000000000100000,
478
+ speak: 0x0000000000200000,
479
+ mute_members: 0x0000000000400000,
480
+ deafen_members: 0x0000000000800000,
481
+ move_members: 0x0000000001000000,
482
+ use_vad: 0x0000000002000000,
483
+ change_nickname: 0x0000000004000000,
484
+ manage_nicknames: 0x0000000008000000,
485
+ manage_roles: 0x0000000010000000,
486
+ manage_webhooks: 0x0000000020000000,
487
+ manage_guild_expressions: 0x0000000040000000,
488
+ use_application_commands: 0x0000000080000000,
489
+ request_to_speak: 0x0000000100000000,
490
+ manage_events: 0x0000000200000000,
491
+ manage_threads: 0x0000000400000000,
492
+ create_public_threads: 0x0000000800000000,
493
+ create_private_threads: 0x0000001000000000,
494
+ use_external_stickers: 0x0000002000000000,
495
+ send_messages_in_threads: 0x0000004000000000,
496
+ use_embedded_activities: 0x0000008000000000,
497
+ moderate_members: 0x0000010000000000,
498
+ view_creator_monetization_analytics: 0x0000020000000000,
499
+ use_soundboard: 0x0000040000000000,
500
+ create_guild_expressions: 0x0000080000000000,
501
+ create_events: 0x0000100000000000,
502
+ use_external_sounds: 0x0000200000000000,
503
+ send_voice_messages: 0x0000400000000000,
504
+ send_polls: 0x0002000000000000,
505
+ use_external_apps: 0x0004000000000000
506
+ }
507
+ permissions = permissions.map do |permission|
508
+ bitwise_permission_flags[permission.downcase.to_sym]
509
+ end
510
+ permissions.reduce(0) { |acc, n| acc | n }
511
+ end
512
+
513
+ def self.reverse_permissions_integer(permissions_integer)
514
+ bitwise_permission_flags = {
515
+ create_instant_invite: 0x0000000000000001,
516
+ kick_members: 0x0000000000000002,
517
+ ban_members: 0x0000000000000004,
518
+ administrator: 0x0000000000000008,
519
+ manage_channels: 0x0000000000000010,
520
+ manage_guild: 0x0000000000000020,
521
+ add_reactions: 0x0000000000000040,
522
+ view_audit_log: 0x0000000000000080,
523
+ priority_speaker: 0x0000000000000100,
524
+ stream: 0x0000000000000200,
525
+ view_channel: 0x0000000000000400,
526
+ send_messages: 0x0000000000000800,
527
+ send_tts_messages: 0x0000000000001000,
528
+ manage_messages: 0x0000000000002000,
529
+ embed_links: 0x0000000000004000,
530
+ attach_files: 0x0000000000008000,
531
+ read_message_history: 0x0000000000010000,
532
+ mention_everyone: 0x0000000000020000,
533
+ use_external_emojis: 0x0000000000040000,
534
+ view_guild_insights: 0x0000000000080000,
535
+ connect: 0x0000000000100000,
536
+ speak: 0x0000000000200000,
537
+ mute_members: 0x0000000000400000,
538
+ deafen_members: 0x0000000000800000,
539
+ move_members: 0x0000000001000000,
540
+ use_vad: 0x0000000002000000,
541
+ change_nickname: 0x0000000004000000,
542
+ manage_nicknames: 0x0000000008000000,
543
+ manage_roles: 0x0000000010000000,
544
+ manage_webhooks: 0x0000000020000000,
545
+ manage_guild_expressions: 0x0000000040000000,
546
+ use_application_commands: 0x0000000080000000,
547
+ request_to_speak: 0x0000000100000000,
548
+ manage_events: 0x0000000200000000,
549
+ manage_threads: 0x0000000400000000,
550
+ create_public_threads: 0x0000000800000000,
551
+ create_private_threads: 0x0000001000000000,
552
+ use_external_stickers: 0x0000002000000000,
553
+ send_messages_in_threads: 0x0000004000000000,
554
+ use_embedded_activities: 0x0000008000000000,
555
+ moderate_members: 0x0000010000000000,
556
+ view_creator_monetization_analytics: 0x0000020000000000,
557
+ use_soundboard: 0x0000040000000000,
558
+ create_guild_expressions: 0x0000080000000000,
559
+ create_events: 0x0000100000000000,
560
+ use_external_sounds: 0x0000200000000000,
561
+ send_voice_messages: 0x0000400000000000,
562
+ send_polls: 0x0002000000000000,
563
+ use_external_apps: 0x0004000000000000
564
+ }
565
+ permissions = []
566
+ bitwise_permission_flags.each do |permission, value|
567
+ permissions << permission if (permissions_integer & value) != 0
568
+ end
569
+ permissions
570
+ end
571
+
572
+ def self.calculate_gateway_intents(intents)
573
+ bitwise_intent_flags = {
574
+ guilds: 1 << 0,
575
+ guild_members: 1 << 1,
576
+ guild_bans: 1 << 2,
577
+ guild_emojis_and_stickers: 1 << 3,
578
+ guild_integrations: 1 << 4,
579
+ guild_webhooks: 1 << 5,
580
+ guild_invites: 1 << 6,
581
+ guild_voice_states: 1 << 7,
582
+ guild_presences: 1 << 8,
583
+ guild_messages: 1 << 9,
584
+ guild_message_reactions: 1 << 10,
585
+ guild_message_typing: 1 << 11,
586
+ direct_messages: 1 << 12,
587
+ direct_message_reactions: 1 << 13,
588
+ direct_message_typing: 1 << 14,
589
+ message_content: 1 << 15,
590
+ guild_scheduled_events: 1 << 16
591
+ }
592
+ intents = intents.map do |intent|
593
+ bitwise_intent_flags[intent.downcase.to_sym]
594
+ end
595
+ intents.reduce(0) { |acc, n| acc | n }
596
+ end
597
+
598
+ # TODO: Transition from Net::HTTP to below wrapper methods
599
+ def self.get(url, headers = nil)
600
+ split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
601
+ @logger.error("Empty/invalid URL provided: #{url}. Cannot perform GET request.") if split_url.empty?
602
+ host = split_url[0]
603
+ path = split_url[1] if split_url[1]
604
+ conn = Faraday.new(url: host, headers: headers)
605
+ if path
606
+ conn.get(path)
607
+ else
608
+ conn.get
609
+ end
610
+ end
611
+
612
+ def self.delete(url, headers = nil)
613
+ split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
614
+ @logger.error("Empty/invalid URL provided: #{url}. Cannot perform DELETE request.") if split_url.empty?
615
+ host = split_url[0]
616
+ path = split_url[1] if split_url[1]
617
+ conn = Faraday.new(url: host, headers: headers)
618
+ if path
619
+ conn.delete(path)
620
+ else
621
+ conn.delete
622
+ end
623
+ end
624
+
625
+ def self.post(url, data, headers = nil)
626
+ split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
627
+ @logger.error("Empty/invalid URL provided: #{url}. Cannot perform POST request.") if split_url.empty?
628
+ host = split_url[0]
629
+ path = split_url[1] if split_url[1]
630
+ conn = Faraday.new(url: host, headers: headers)
631
+ if path
632
+ conn.post(path, data)
633
+ else
634
+ conn.post('', data)
635
+ end
636
+ end
637
+ end