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,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ # API calls for User object
4
+ module OnyxCord::API::User
5
+ module_function
6
+
7
+ # Get user data
8
+ # https://discord.com/developers/docs/resources/user#get-user
9
+ def resolve(token, user_id)
10
+ OnyxCord::API.request(
11
+ :users_uid,
12
+ nil,
13
+ :get,
14
+ "#{OnyxCord::API.api_base}/users/#{user_id}",
15
+ Authorization: token
16
+ )
17
+ end
18
+
19
+ # Get profile data
20
+ # https://discord.com/developers/docs/resources/user#get-current-user
21
+ def profile(token)
22
+ OnyxCord::API.request(
23
+ :users_me,
24
+ nil,
25
+ :get,
26
+ "#{OnyxCord::API.api_base}/users/@me",
27
+ Authorization: token
28
+ )
29
+ end
30
+
31
+ # @deprecated Please use {OnyxCord::API::Server.update_current_member} instead.
32
+ # https://discord.com/developers/docs/resources/user#modify-current-user-nick
33
+ def change_own_nickname(token, server_id, nick, reason = nil)
34
+ OnyxCord::API.request(
35
+ :guilds_sid_members_me_nick,
36
+ server_id, # This is technically a guild endpoint
37
+ :patch,
38
+ "#{OnyxCord::API.api_base}/guilds/#{server_id}/members/@me/nick",
39
+ { nick: nick }.to_json,
40
+ Authorization: token,
41
+ content_type: :json,
42
+ 'X-Audit-Log-Reason': reason
43
+ )
44
+ end
45
+
46
+ # @deprecated Please use {update_current_user} instead.
47
+ # https://discord.com/developers/docs/resources/user#modify-current-user
48
+ def update_profile(token, _email, _password, new_username, avatar, _new_password = nil)
49
+ update_current_user(token, new_username, avatar)
50
+ end
51
+
52
+ # Update the properties of the user for the current bot.
53
+ # https://discord.com/developers/docs/resources/user#modify-current-user
54
+ def update_current_user(token, username = :undef, avatar = :undef, banner = :undef)
55
+ OnyxCord::API.request(
56
+ :users_me,
57
+ nil,
58
+ :patch,
59
+ "#{OnyxCord::API.api_base}/users/@me",
60
+ { username: username, avatar: avatar, banner: banner }.reject { |_, value| value == :undef }.to_json,
61
+ Authorization: token,
62
+ content_type: :json
63
+ )
64
+ end
65
+
66
+ # Get the servers a user is connected to
67
+ # https://discord.com/developers/docs/resources/user#get-current-user-guilds
68
+ def servers(token)
69
+ OnyxCord::API.request(
70
+ :users_me_guilds,
71
+ nil,
72
+ :get,
73
+ "#{OnyxCord::API.api_base}/users/@me/guilds",
74
+ Authorization: token
75
+ )
76
+ end
77
+
78
+ # Leave a server
79
+ # https://discord.com/developers/docs/resources/user#leave-guild
80
+ def leave_server(token, server_id)
81
+ OnyxCord::API.request(
82
+ :users_me_guilds_sid,
83
+ nil,
84
+ :delete,
85
+ "#{OnyxCord::API.api_base}/users/@me/guilds/#{server_id}",
86
+ Authorization: token
87
+ )
88
+ end
89
+
90
+ # Get the DMs for the current user
91
+ # https://discord.com/developers/docs/resources/user#get-user-dms
92
+ def user_dms(token)
93
+ OnyxCord::API.request(
94
+ :users_me_channels,
95
+ nil,
96
+ :get,
97
+ "#{OnyxCord::API.api_base}/users/@me/channels",
98
+ Authorization: token
99
+ )
100
+ end
101
+
102
+ # Create a DM to another user
103
+ # https://discord.com/developers/docs/resources/user#create-dm
104
+ def create_pm(token, recipient_id)
105
+ OnyxCord::API.request(
106
+ :users_me_channels,
107
+ nil,
108
+ :post,
109
+ "#{OnyxCord::API.api_base}/users/@me/channels",
110
+ { recipient_id: recipient_id }.to_json,
111
+ Authorization: token,
112
+ content_type: :json
113
+ )
114
+ end
115
+
116
+ # Get information about a user's connections
117
+ # https://discord.com/developers/docs/resources/user#get-users-connections
118
+ def connections(token)
119
+ OnyxCord::API.request(
120
+ :users_me_connections,
121
+ nil,
122
+ :get,
123
+ "#{OnyxCord::API.api_base}/users/@me/connections",
124
+ Authorization: token
125
+ )
126
+ end
127
+
128
+ # Returns one of the "default" discord avatars from the CDN given a discriminator or id since new usernames
129
+ # TODO: Maybe change this method again after discriminator removal ?
130
+ def default_avatar(discrim_id = 0, legacy: false)
131
+ index = if legacy
132
+ discrim_id.to_i % 5
133
+ else
134
+ (discrim_id.to_i >> 22) % 5
135
+ end
136
+ "#{OnyxCord::API.cdn_url}/embed/avatars/#{index}.png"
137
+ end
138
+
139
+ # Make an avatar URL from the user and avatar IDs
140
+ def avatar_url(user_id, avatar_id, format = nil)
141
+ format ||= if avatar_id.start_with?('a_')
142
+ 'gif'
143
+ else
144
+ 'webp'
145
+ end
146
+ "#{OnyxCord::API.cdn_url}/avatars/#{user_id}/#{avatar_id}.#{format}"
147
+ end
148
+
149
+ # Make a banner URL from the user and banner IDs
150
+ def banner_url(user_id, banner_id, format = nil)
151
+ format ||= if banner_id.start_with?('a_')
152
+ 'gif'
153
+ else
154
+ 'png'
155
+ end
156
+ "#{OnyxCord::API.cdn_url}/banners/#{user_id}/#{banner_id}.#{format}"
157
+ end
158
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'onyxcord/message_components'
4
+
5
+ # API calls for Webhook object
6
+ module OnyxCord::API::Webhook
7
+ module_function
8
+
9
+ # Get a webhook
10
+ # https://discord.com/developers/docs/resources/webhook#get-webhook
11
+ def webhook(token, webhook_id)
12
+ OnyxCord::API.request(
13
+ :webhooks_wid,
14
+ nil,
15
+ :get,
16
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}",
17
+ Authorization: token
18
+ )
19
+ end
20
+
21
+ # Get a webhook via webhook token
22
+ # https://discord.com/developers/docs/resources/webhook#get-webhook-with-token
23
+ def token_webhook(webhook_token, webhook_id)
24
+ OnyxCord::API.request(
25
+ :webhooks_wid,
26
+ nil,
27
+ :get,
28
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}"
29
+ )
30
+ end
31
+
32
+ # Execute a webhook via token.
33
+ # https://discord.com/developers/docs/resources/webhook#execute-webhook
34
+ def token_execute_webhook(webhook_token, webhook_id, wait = false, content = nil, username = nil, avatar_url = nil, tts = nil, file = nil, embeds = nil, allowed_mentions = nil, flags = nil, components = nil, attachments = nil, poll = nil)
35
+ components = OnyxCord::MessageComponents.payload(components) unless components.nil?
36
+ flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
37
+ body = { content: content, username: username, avatar_url: avatar_url, tts: tts, embeds: embeds&.map(&:to_hash), allowed_mentions: allowed_mentions, flags: flags, components: components, poll: poll }
38
+
39
+ body = if file
40
+ { file: file, payload_json: body.to_json }
41
+ elsif attachments
42
+ files = [*0...attachments.size].zip(attachments).to_h
43
+ { **files, payload_json: body.to_json }
44
+ else
45
+ body.to_json
46
+ end
47
+
48
+ headers = { content_type: :json } unless file || attachments
49
+ with_components = components&.any? ? true : nil
50
+ query = URI.encode_www_form({ wait: wait, with_components: with_components }.compact)
51
+
52
+ OnyxCord::API.request(
53
+ :webhooks_wid,
54
+ webhook_id,
55
+ :post,
56
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}?#{query}",
57
+ body,
58
+ headers
59
+ )
60
+ end
61
+
62
+ # Update a webhook
63
+ # https://discord.com/developers/docs/resources/webhook#modify-webhook
64
+ def update_webhook(token, webhook_id, data, reason = nil)
65
+ OnyxCord::API.request(
66
+ :webhooks_wid,
67
+ webhook_id,
68
+ :patch,
69
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}",
70
+ data.to_json,
71
+ Authorization: token,
72
+ content_type: :json,
73
+ 'X-Audit-Log-Reason': reason
74
+ )
75
+ end
76
+
77
+ # Update a webhook via webhook token
78
+ # https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token
79
+ def token_update_webhook(webhook_token, webhook_id, data, reason = nil)
80
+ OnyxCord::API.request(
81
+ :webhooks_wid,
82
+ webhook_id,
83
+ :patch,
84
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
85
+ data.to_json,
86
+ content_type: :json,
87
+ 'X-Audit-Log-Reason': reason
88
+ )
89
+ end
90
+
91
+ # Deletes a webhook
92
+ # https://discord.com/developers/docs/resources/webhook#delete-webhook
93
+ def delete_webhook(token, webhook_id, reason = nil)
94
+ OnyxCord::API.request(
95
+ :webhooks_wid,
96
+ webhook_id,
97
+ :delete,
98
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}",
99
+ Authorization: token,
100
+ 'X-Audit-Log-Reason': reason
101
+ )
102
+ end
103
+
104
+ # Deletes a webhook via webhook token
105
+ # https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token
106
+ def token_delete_webhook(webhook_token, webhook_id, reason = nil)
107
+ OnyxCord::API.request(
108
+ :webhooks_wid,
109
+ webhook_id,
110
+ :delete,
111
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
112
+ 'X-Audit-Log-Reason': reason
113
+ )
114
+ end
115
+
116
+ # Get a message that was created by the webhook corresponding to the provided token.
117
+ # https://discord.com/developers/docs/resources/webhook#get-webhook-message
118
+ def token_get_message(webhook_token, webhook_id, message_id)
119
+ OnyxCord::API.request(
120
+ :webhooks_wid_messages_mid,
121
+ webhook_id,
122
+ :get,
123
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}/messages/#{message_id}"
124
+ )
125
+ end
126
+
127
+ # Edit a webhook message via webhook token
128
+ # https://discord.com/developers/docs/resources/webhook#edit-webhook-message
129
+ def token_edit_message(webhook_token, webhook_id, message_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil, flags = nil, poll = nil)
130
+ components = OnyxCord::MessageComponents.payload(components) unless components.nil?
131
+ flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
132
+ body = { content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, flags: flags, poll: poll }
133
+
134
+ body = if attachments
135
+ files = [*0...attachments.size].zip(attachments).to_h
136
+ { **files, payload_json: body.to_json }
137
+ else
138
+ body.to_json
139
+ end
140
+
141
+ headers = { content_type: :json } unless attachments
142
+
143
+ OnyxCord::API.request(
144
+ :webhooks_wid_messages,
145
+ webhook_id,
146
+ :patch,
147
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}/messages/#{message_id}",
148
+ body,
149
+ headers
150
+ )
151
+ end
152
+
153
+ # Delete a webhook message via webhook token.
154
+ # https://discord.com/developers/docs/resources/webhook#delete-webhook-message
155
+ def token_delete_message(webhook_token, webhook_id, message_id)
156
+ OnyxCord::API.request(
157
+ :webhooks_wid_messages,
158
+ webhook_id,
159
+ :delete,
160
+ "#{OnyxCord::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}/messages/#{message_id}"
161
+ )
162
+ end
163
+ end
@@ -0,0 +1,335 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rest-client'
4
+ require 'json'
5
+ require 'time'
6
+
7
+ require 'onyxcord/errors'
8
+ require 'onyxcord/rate_limiter/rest'
9
+
10
+ # List of methods representing endpoints in Discord's API
11
+ module OnyxCord::API
12
+ # The base URL of the Discord REST API.
13
+ APIBASE = 'https://discord.com/api/v9'
14
+
15
+ # The URL of Discord's CDN
16
+ CDN_URL = 'https://cdn.discordapp.com'
17
+
18
+ module_function
19
+
20
+ # @return [String] the currently used API base URL.
21
+ def api_base
22
+ @api_base || APIBASE
23
+ end
24
+
25
+ # Sets the API base URL to something.
26
+ def api_base=(value)
27
+ @api_base = value
28
+ end
29
+
30
+ # @return [String] the currently used CDN url
31
+ def cdn_url
32
+ @cdn_url || CDN_URL
33
+ end
34
+
35
+ # @return [String] the bot name, previously specified using {.bot_name=}.
36
+ def bot_name
37
+ @bot_name
38
+ end
39
+
40
+ # Sets the bot name to something. Used in {.user_agent}. For the bot's username, see {Profile#username=}.
41
+ def bot_name=(value)
42
+ @bot_name = value
43
+ end
44
+
45
+ # Changes the rate limit tracing behaviour. If rate limit tracing is on, a full backtrace will be logged on every RL
46
+ # hit.
47
+ # @param value [true, false] whether or not to enable rate limit tracing
48
+ def trace=(value)
49
+ @trace = value
50
+ end
51
+
52
+ # Generate a user agent identifying this requester as onyxcord.
53
+ def user_agent
54
+ # This particular string is required by the Discord devs.
55
+ required = "DiscordBot (https://github.com/kruldevb/OnyxCord, v#{OnyxCord::VERSION})"
56
+ @bot_name ||= ''
57
+
58
+ "#{required} rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} onyxcord/#{OnyxCord::VERSION} #{@bot_name}"
59
+ end
60
+
61
+ # Resets all rate limit mutexes
62
+ def reset_mutexes
63
+ @rate_limiter = OnyxCord::RateLimiter::Rest.new
64
+ end
65
+
66
+ def rate_limiter
67
+ @rate_limiter ||= OnyxCord::RateLimiter::Rest.new
68
+ end
69
+
70
+ # Wait a specified amount of time synchronised with the specified mutex.
71
+ def sync_wait(time, mutex)
72
+ mutex.synchronize { sleep time }
73
+ end
74
+
75
+ # Wait for a specified mutex to unlock and do nothing with it afterwards.
76
+ def mutex_wait(mutex)
77
+ mutex.lock
78
+ mutex.unlock
79
+ end
80
+
81
+ # Performs a RestClient request.
82
+ # @param type [Symbol] The type of HTTP request to use.
83
+ # @param attributes [Array] The attributes for the request.
84
+ def raw_request(type, attributes)
85
+ RestClient.send(type, *attributes)
86
+ rescue RestClient::Forbidden => e
87
+ # HACK: for #request, dynamically inject restclient's response into NoPermission - this allows us to rate limit
88
+ noprm = OnyxCord::Errors::NoPermission.new
89
+ noprm.define_singleton_method(:_rc_response) { e.response }
90
+ raise noprm, "The bot doesn't have the required permission to do this!"
91
+ rescue RestClient::BadGateway
92
+ OnyxCord::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
93
+ retry
94
+ end
95
+
96
+ # Make an API request, including rate limit handling.
97
+ def request(key, major_parameter, type, *attributes)
98
+ # Add a custom user agent
99
+ attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash
100
+
101
+ begin
102
+ rate_limiter.before_request(key, major_parameter)
103
+
104
+ response = nil
105
+ begin
106
+ response = raw_request(type, attributes)
107
+ rescue RestClient::Exception => e
108
+ response = e.response
109
+
110
+ if response.body && !e.is_a?(RestClient::TooManyRequests)
111
+ data = JSON.parse(response.body)
112
+ err_klass = OnyxCord::Errors.error_class_for(data['code'] || 0)
113
+ e = err_klass.new(data['message'], data['errors'])
114
+
115
+ OnyxCord::LOGGER.error(e.full_message)
116
+ end
117
+
118
+ raise e
119
+ rescue OnyxCord::Errors::NoPermission => e
120
+ if e.respond_to?(:_rc_response)
121
+ response = e._rc_response
122
+ else
123
+ OnyxCord::LOGGER.warn("NoPermission doesn't respond_to? _rc_response!")
124
+ end
125
+
126
+ raise e
127
+ ensure
128
+ if response
129
+ rate_limiter.record_response(key, major_parameter, response.headers)
130
+ else
131
+ OnyxCord::LOGGER.ratelimit('Response was nil before trying to preemptively rate limit!')
132
+ end
133
+ end
134
+ rescue RestClient::TooManyRequests => e
135
+ trace("429 #{key} #{major_parameter}")
136
+ rate_limiter.handle_rate_limit(key, major_parameter, e.response)
137
+
138
+ retry
139
+ end
140
+
141
+ # Endpoints that use Elasticsearch can return a 202 when the index isn't ready yet. Wait the
142
+ # amount of time indicated by the response body, and then recursively retry and return the request.
143
+ if response&.code == 202 && response&.body
144
+ body = JSON.parse(response.body)
145
+
146
+ if body['code'] == 110_000
147
+ case body['retry_after']
148
+ when 0, 1, nil
149
+ sleep(rand(4.5..5.0))
150
+ else
151
+ sleep(body['retry_after'])
152
+ end
153
+
154
+ return request(*key, type, *attributes)
155
+ end
156
+ end
157
+
158
+ response
159
+ end
160
+
161
+ # Handles pre-emptive rate limiting by waiting the given mutex by the difference of the Date header to the
162
+ # X-Ratelimit-Reset header, thus making sure we don't get 429'd in any subsequent requests.
163
+ def handle_preemptive_rl(headers, mutex, key)
164
+ OnyxCord::LOGGER.ratelimit "RL bucket depletion detected! Date: #{headers[:date]} Reset: #{headers[:x_ratelimit_reset]}"
165
+ delta = headers[:x_ratelimit_reset_after].to_f
166
+ OnyxCord::LOGGER.warn("Locking RL mutex (key: #{key}) for #{delta} seconds pre-emptively")
167
+ sync_wait(delta, mutex)
168
+ end
169
+
170
+ # Perform rate limit tracing. All this method does is log the current backtrace to the console with the `:ratelimit`
171
+ # level.
172
+ # @param reason [String] the reason to include with the backtrace.
173
+ def trace(reason)
174
+ unless @trace
175
+ OnyxCord::LOGGER.debug("trace was called with reason #{reason}, but tracing is not enabled")
176
+ return
177
+ end
178
+
179
+ OnyxCord::LOGGER.ratelimit("Trace (#{reason}):")
180
+
181
+ caller.each do |str|
182
+ OnyxCord::LOGGER.ratelimit(" #{str}")
183
+ end
184
+ end
185
+
186
+ # Make an icon URL from server and icon IDs
187
+ def icon_url(server_id, icon_id, format = 'webp')
188
+ "#{cdn_url}/icons/#{server_id}/#{icon_id}.#{format}"
189
+ end
190
+
191
+ # Make an icon URL from application and icon IDs
192
+ def app_icon_url(app_id, icon_id, format = 'webp')
193
+ "#{cdn_url}/app-icons/#{app_id}/#{icon_id}.#{format}"
194
+ end
195
+
196
+ # Make a widget picture URL from server ID
197
+ def widget_url(server_id, style = 'shield')
198
+ "#{api_base}/guilds/#{server_id}/widget.png?style=#{style}"
199
+ end
200
+
201
+ # Make a splash URL from server and splash IDs
202
+ def splash_url(server_id, splash_id, format = 'webp')
203
+ "#{cdn_url}/splashes/#{server_id}/#{splash_id}.#{format}"
204
+ end
205
+
206
+ # Make a discovery splash URL from server and splash IDs
207
+ def discovery_splash_url(server_id, splash_id, format = 'webp')
208
+ "#{cdn_url}/discovery-splashes/#{server_id}/#{splash_id}.#{format}"
209
+ end
210
+
211
+ # Make a banner URL from server and banner IDs
212
+ def banner_url(server_id, banner_id, format = 'webp')
213
+ "#{cdn_url}/banners/#{server_id}/#{banner_id}.#{format}"
214
+ end
215
+
216
+ # Make an emoji icon URL from emoji ID
217
+ def emoji_icon_url(emoji_id, format = 'webp')
218
+ "#{cdn_url}/emojis/#{emoji_id}.#{format}"
219
+ end
220
+
221
+ # Make an asset URL from application and asset IDs
222
+ def asset_url(application_id, asset_id, format = 'webp')
223
+ "#{cdn_url}/app-assets/#{application_id}/#{asset_id}.#{format}"
224
+ end
225
+
226
+ # Make an achievement icon URL from application ID, achievement ID, and icon hash
227
+ def achievement_icon_url(application_id, achievement_id, icon_hash, format = 'webp')
228
+ "#{cdn_url}/app-assets/#{application_id}/achievements/#{achievement_id}/icons/#{icon_hash}.#{format}"
229
+ end
230
+
231
+ # @param role_id [String, Integer]
232
+ # @param icon_hash [String]
233
+ # @param format ['webp', 'png', 'jpeg']
234
+ # @return [String]
235
+ def role_icon_url(role_id, icon_hash, format = 'webp')
236
+ "#{cdn_url}/role-icons/#{role_id}/#{icon_hash}.#{format}"
237
+ end
238
+
239
+ # make an avatar decoration URL from an avatar decoration ID.
240
+ def avatar_decoration_url(avatar_decoration_id, format = 'png')
241
+ "#{cdn_url}/avatar-decoration-presets/#{avatar_decoration_id}.#{format}"
242
+ end
243
+
244
+ # make a static nameplate URL from the nameplate asset.
245
+ def static_nameplate_url(nameplate_asset, format = 'png')
246
+ "#{cdn_url}/assets/collectibles/#{nameplate_asset.delete_suffix('/')}/static.#{format}"
247
+ end
248
+
249
+ # make a nameplate URL from the nameplate asset.
250
+ def nameplate_url(nameplate_asset, format = 'webm')
251
+ "#{cdn_url}/assets/collectibles/#{nameplate_asset.delete_suffix('/')}/asset.#{format}"
252
+ end
253
+
254
+ # make a server tag badge URL from a server ID and badge ID.
255
+ def server_tag_badge_url(server_id, badge_id, format = 'webp')
256
+ "#{cdn_url}/guild-tag-badges/#{server_id}/#{badge_id}.#{format}"
257
+ end
258
+
259
+ # make a scheduled event cover URL from a scheduled event ID and a cover ID.
260
+ def scheduled_event_cover_url(scheduled_event_id, cover_id, format = 'webp', size = nil)
261
+ "#{cdn_url}/guild-events/#{scheduled_event_id}/#{cover_id}.#{format}#{"?size=#{size}" if size}"
262
+ end
263
+
264
+ # make a cover image URL from application and cover IDs.
265
+ def app_cover_url(app_id, cover_id, format = 'webp')
266
+ "#{cdn_url}/app-icons/#{app_id}/#{cover_id}.#{format}"
267
+ end
268
+
269
+ # make a team icon URL from team and icon IDs.
270
+ def team_icon_url(team_id, icon_id, format = 'webp')
271
+ "#{cdn_url}/team-icons/#{team_id}/#{icon_id}.#{format}"
272
+ end
273
+
274
+ # Change an OAuth application's properties
275
+ # @deprecated Please use {Application#update_current_application} instead.
276
+ def update_oauth_application(token, name, redirect_uris, description = '', icon = nil)
277
+ request(
278
+ :oauth2_applications,
279
+ nil,
280
+ :put,
281
+ "#{api_base}/oauth2/applications",
282
+ { name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
283
+ Authorization: token,
284
+ content_type: :json
285
+ )
286
+ end
287
+
288
+ # Get the bot's OAuth application's information
289
+ def oauth_application(token)
290
+ request(
291
+ :oauth2_applications_me,
292
+ nil,
293
+ :get,
294
+ "#{api_base}/applications/@me",
295
+ Authorization: token
296
+ )
297
+ end
298
+
299
+ # Get the gateway to be used
300
+ def gateway(token)
301
+ request(
302
+ :gateway,
303
+ nil,
304
+ :get,
305
+ "#{api_base}/gateway",
306
+ Authorization: token
307
+ )
308
+ end
309
+
310
+ # Get the gateway to be used, with additional information for sharding and
311
+ # session start limits
312
+ def gateway_bot(token)
313
+ request(
314
+ :gateway_bot,
315
+ nil,
316
+ :get,
317
+ "#{api_base}/gateway/bot",
318
+ Authorization: token
319
+ )
320
+ end
321
+
322
+ # Get a list of available voice regions
323
+ def voice_regions(token)
324
+ request(
325
+ :voice_regions,
326
+ nil,
327
+ :get,
328
+ "#{api_base}/voice/regions",
329
+ Authorization: token,
330
+ content_type: :json
331
+ )
332
+ end
333
+ end
334
+
335
+ OnyxCord::API.reset_mutexes
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord
4
+ # Awaits are a way to register new, temporary event handlers on the fly. Awaits can be
5
+ # registered using {Bot#add_await}, {User#await}, {Message#await} and {Channel#await}.
6
+ #
7
+ # Awaits contain a block that will be called before the await event will be triggered.
8
+ # If this block returns anything that is not `false` exactly, the await will be deleted.
9
+ # If no block is present, the await will also be deleted. This is an easy way to make
10
+ # temporary events that are only temporary under certain conditions.
11
+ #
12
+ # Besides the given block, an {OnyxCord::Events::AwaitEvent} will also be executed with the key and
13
+ # the type of the await that was triggered. It's possible to register multiple events
14
+ # that trigger on the same await.
15
+ class Await
16
+ # The key that uniquely identifies this await.
17
+ # @return [Symbol] The unique key.
18
+ attr_reader :key
19
+
20
+ # The class of the event that this await listens for.
21
+ # @return [Class] The event class.
22
+ attr_reader :type
23
+
24
+ # The attributes of the event that will be listened for.
25
+ # @return [Hash] A hash of attributes.
26
+ attr_reader :attributes
27
+
28
+ # Makes a new await. For internal use only.
29
+ # @!visibility private
30
+ def initialize(bot, key, type, attributes, block = nil)
31
+ @bot = bot
32
+ @key = key
33
+ @type = type
34
+ @attributes = attributes
35
+ @block = block
36
+ end
37
+
38
+ # Checks whether the await can be triggered by the given event, and if it can, execute the block
39
+ # and return its result along with this await's key.
40
+ # @param event [Event] An event to check for.
41
+ # @return [Array] This await's key and whether or not it should be deleted. If there was no match, both are nil.
42
+ def match(event)
43
+ dummy_handler = EventContainer.handler_class(@type).new(@attributes, @bot)
44
+ return [nil, nil] unless event.instance_of?(@type) && dummy_handler.matches?(event)
45
+
46
+ should_delete = true if (@block && @block.call(event) != false) || !@block
47
+
48
+ [@key, should_delete]
49
+ end
50
+ end
51
+ end