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.
- checksums.yaml +7 -0
- data/.devcontainer/Dockerfile +13 -0
- data/.devcontainer/devcontainer.json +29 -0
- data/.devcontainer/postcreate.sh +4 -0
- data/.github/CONTRIBUTING.md +13 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/ci.yml +78 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.github/workflows/deploy.yml +54 -0
- data/.github/workflows/release.yml +51 -0
- data/.gitignore +16 -0
- data/.markdownlint.json +4 -0
- data/.overcommit.yml +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +129 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +305 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/lib/onyxcord/allowed_mentions.rb +43 -0
- data/lib/onyxcord/api/application.rb +316 -0
- data/lib/onyxcord/api/channel.rb +700 -0
- data/lib/onyxcord/api/interaction.rb +67 -0
- data/lib/onyxcord/api/invite.rb +44 -0
- data/lib/onyxcord/api/server.rb +775 -0
- data/lib/onyxcord/api/user.rb +158 -0
- data/lib/onyxcord/api/webhook.rb +163 -0
- data/lib/onyxcord/api.rb +335 -0
- data/lib/onyxcord/await.rb +51 -0
- data/lib/onyxcord/bot.rb +1971 -0
- data/lib/onyxcord/cache.rb +326 -0
- data/lib/onyxcord/colour_rgb.rb +43 -0
- data/lib/onyxcord/commands/command_bot.rb +511 -0
- data/lib/onyxcord/commands/container.rb +112 -0
- data/lib/onyxcord/commands/events.rb +11 -0
- data/lib/onyxcord/commands/parser.rb +327 -0
- data/lib/onyxcord/commands/rate_limiter.rb +144 -0
- data/lib/onyxcord/configuration.rb +125 -0
- data/lib/onyxcord/container.rb +988 -0
- data/lib/onyxcord/data/activity.rb +271 -0
- data/lib/onyxcord/data/application.rb +341 -0
- data/lib/onyxcord/data/attachment.rb +91 -0
- data/lib/onyxcord/data/audit_logs.rb +438 -0
- data/lib/onyxcord/data/avatar_decoration.rb +26 -0
- data/lib/onyxcord/data/call.rb +22 -0
- data/lib/onyxcord/data/channel.rb +1355 -0
- data/lib/onyxcord/data/channel_tag.rb +69 -0
- data/lib/onyxcord/data/collectibles.rb +47 -0
- data/lib/onyxcord/data/component.rb +583 -0
- data/lib/onyxcord/data/embed.rb +258 -0
- data/lib/onyxcord/data/emoji.rb +123 -0
- data/lib/onyxcord/data/install_params.rb +24 -0
- data/lib/onyxcord/data/integration.rb +144 -0
- data/lib/onyxcord/data/interaction.rb +1141 -0
- data/lib/onyxcord/data/invite.rb +137 -0
- data/lib/onyxcord/data/member.rb +528 -0
- data/lib/onyxcord/data/message.rb +612 -0
- data/lib/onyxcord/data/message_activity.rb +41 -0
- data/lib/onyxcord/data/overwrite.rb +109 -0
- data/lib/onyxcord/data/poll.rb +365 -0
- data/lib/onyxcord/data/primary_server.rb +60 -0
- data/lib/onyxcord/data/profile.rb +79 -0
- data/lib/onyxcord/data/reaction.rb +64 -0
- data/lib/onyxcord/data/recipient.rb +34 -0
- data/lib/onyxcord/data/role.rb +449 -0
- data/lib/onyxcord/data/role_connection_data.rb +69 -0
- data/lib/onyxcord/data/role_subscription.rb +41 -0
- data/lib/onyxcord/data/scheduled_event.rb +513 -0
- data/lib/onyxcord/data/server.rb +1614 -0
- data/lib/onyxcord/data/server_preview.rb +68 -0
- data/lib/onyxcord/data/snapshot.rb +112 -0
- data/lib/onyxcord/data/team.rb +98 -0
- data/lib/onyxcord/data/timestamp.rb +69 -0
- data/lib/onyxcord/data/user.rb +324 -0
- data/lib/onyxcord/data/voice_region.rb +46 -0
- data/lib/onyxcord/data/voice_state.rb +41 -0
- data/lib/onyxcord/data/webhook.rb +238 -0
- data/lib/onyxcord/data.rb +57 -0
- data/lib/onyxcord/errors.rb +246 -0
- data/lib/onyxcord/event_executor.rb +80 -0
- data/lib/onyxcord/events/await.rb +48 -0
- data/lib/onyxcord/events/bans.rb +60 -0
- data/lib/onyxcord/events/channels.rb +225 -0
- data/lib/onyxcord/events/generic.rb +129 -0
- data/lib/onyxcord/events/guilds.rb +269 -0
- data/lib/onyxcord/events/integrations.rb +100 -0
- data/lib/onyxcord/events/interactions.rb +624 -0
- data/lib/onyxcord/events/invites.rb +127 -0
- data/lib/onyxcord/events/lifetime.rb +31 -0
- data/lib/onyxcord/events/members.rb +110 -0
- data/lib/onyxcord/events/message.rb +399 -0
- data/lib/onyxcord/events/polls.rb +118 -0
- data/lib/onyxcord/events/presence.rb +131 -0
- data/lib/onyxcord/events/raw.rb +74 -0
- data/lib/onyxcord/events/reactions.rb +218 -0
- data/lib/onyxcord/events/roles.rb +87 -0
- data/lib/onyxcord/events/scheduled_events.rb +171 -0
- data/lib/onyxcord/events/threads.rb +100 -0
- data/lib/onyxcord/events/typing.rb +73 -0
- data/lib/onyxcord/events/voice_server_update.rb +48 -0
- data/lib/onyxcord/events/voice_state_update.rb +106 -0
- data/lib/onyxcord/events/webhooks.rb +65 -0
- data/lib/onyxcord/gateway.rb +890 -0
- data/lib/onyxcord/id_object.rb +39 -0
- data/lib/onyxcord/light/data.rb +62 -0
- data/lib/onyxcord/light/integrations.rb +73 -0
- data/lib/onyxcord/light/light_bot.rb +58 -0
- data/lib/onyxcord/light.rb +8 -0
- data/lib/onyxcord/logger.rb +120 -0
- data/lib/onyxcord/message_components.rb +70 -0
- data/lib/onyxcord/paginator.rb +60 -0
- data/lib/onyxcord/permissions.rb +255 -0
- data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
- data/lib/onyxcord/rate_limiter/rest.rb +89 -0
- data/lib/onyxcord/version.rb +7 -0
- data/lib/onyxcord/voice/encoder.rb +115 -0
- data/lib/onyxcord/voice/network.rb +380 -0
- data/lib/onyxcord/voice/opcodes.rb +29 -0
- data/lib/onyxcord/voice/sodium.rb +157 -0
- data/lib/onyxcord/voice/timer.rb +19 -0
- data/lib/onyxcord/voice/voice_bot.rb +386 -0
- data/lib/onyxcord/webhooks.rb +14 -0
- data/lib/onyxcord/websocket.rb +62 -0
- data/lib/onyxcord.rb +180 -0
- data/onyxcord-webhooks.gemspec +30 -0
- data/onyxcord.gemspec +50 -0
- 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
|
data/lib/onyxcord/api.rb
ADDED
|
@@ -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
|