discorb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/.yardopts +6 -0
  4. data/Changelog.md +5 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +70 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +53 -0
  9. data/Rakefile +46 -0
  10. data/bin/console +15 -0
  11. data/bin/setup +8 -0
  12. data/discorb.gemspec +37 -0
  13. data/docs/Examples.md +26 -0
  14. data/docs/events.md +480 -0
  15. data/docs/voice_events.md +283 -0
  16. data/examples/components/authorization_button.rb +43 -0
  17. data/examples/components/select_menu.rb +61 -0
  18. data/examples/extension/main.rb +12 -0
  19. data/examples/extension/message_expander.rb +41 -0
  20. data/examples/simple/eval.rb +32 -0
  21. data/examples/simple/ping_pong.rb +16 -0
  22. data/examples/simple/rolepanel.rb +65 -0
  23. data/examples/simple/wait_for_message.rb +30 -0
  24. data/lib/discorb/application.rb +157 -0
  25. data/lib/discorb/asset.rb +57 -0
  26. data/lib/discorb/audit_logs.rb +323 -0
  27. data/lib/discorb/channel.rb +1101 -0
  28. data/lib/discorb/client.rb +363 -0
  29. data/lib/discorb/color.rb +173 -0
  30. data/lib/discorb/common.rb +123 -0
  31. data/lib/discorb/components.rb +290 -0
  32. data/lib/discorb/dictionary.rb +119 -0
  33. data/lib/discorb/embed.rb +345 -0
  34. data/lib/discorb/emoji.rb +218 -0
  35. data/lib/discorb/emoji_table.rb +3799 -0
  36. data/lib/discorb/error.rb +98 -0
  37. data/lib/discorb/event.rb +35 -0
  38. data/lib/discorb/extend.rb +18 -0
  39. data/lib/discorb/extension.rb +54 -0
  40. data/lib/discorb/file.rb +69 -0
  41. data/lib/discorb/flag.rb +109 -0
  42. data/lib/discorb/gateway.rb +967 -0
  43. data/lib/discorb/gateway_requests.rb +47 -0
  44. data/lib/discorb/guild.rb +1244 -0
  45. data/lib/discorb/guild_template.rb +211 -0
  46. data/lib/discorb/image.rb +43 -0
  47. data/lib/discorb/integration.rb +111 -0
  48. data/lib/discorb/intents.rb +137 -0
  49. data/lib/discorb/interaction.rb +333 -0
  50. data/lib/discorb/internet.rb +285 -0
  51. data/lib/discorb/invite.rb +145 -0
  52. data/lib/discorb/log.rb +70 -0
  53. data/lib/discorb/member.rb +232 -0
  54. data/lib/discorb/message.rb +583 -0
  55. data/lib/discorb/modules.rb +138 -0
  56. data/lib/discorb/permission.rb +270 -0
  57. data/lib/discorb/presence.rb +308 -0
  58. data/lib/discorb/reaction.rb +48 -0
  59. data/lib/discorb/role.rb +189 -0
  60. data/lib/discorb/sticker.rb +157 -0
  61. data/lib/discorb/user.rb +163 -0
  62. data/lib/discorb/utils.rb +16 -0
  63. data/lib/discorb/voice_state.rb +251 -0
  64. data/lib/discorb/webhook.rb +420 -0
  65. data/lib/discorb.rb +51 -0
  66. metadata +120 -0
@@ -0,0 +1,363 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "logger"
5
+
6
+ require "async"
7
+ require "async/websocket/client"
8
+
9
+ module Discorb
10
+ #
11
+ # Class for connecting to the Discord server.
12
+ #
13
+ class Client
14
+ # @return [Discorb::Intents] The intents that the client is currently using.
15
+ attr_accessor :intents
16
+ # @return [Discorb::Application] The application that the client is using.
17
+ # @return [nil] If never fetched application by {#fetch_application}.
18
+ attr_reader :application
19
+ # @return [Discorb::Internet] The internet client.
20
+ attr_reader :internet
21
+ # @return [Integer] The heartbeat interval.
22
+ attr_reader :heartbeat_interval
23
+ # @return [Integer] The API version of the Discord gateway.
24
+ # @return [nil] If not connected to the gateway.
25
+ attr_reader :api_version
26
+ # @return [String] The token of the client.
27
+ attr_reader :token
28
+ # @return [Discorb::AllowedMentions] The allowed mentions that the client is using.
29
+ attr_reader :allowed_mentions
30
+ # @return [Discorb::ClientUser] The client user.
31
+ attr_reader :user
32
+ # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Guild}] A dictionary of guilds.
33
+ attr_reader :guilds
34
+ # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::User}] A dictionary of users.
35
+ attr_reader :users
36
+ # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Channel}] A dictionary of channels.
37
+ attr_reader :channels
38
+ # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Emoji}] A dictionary of emojis.
39
+ attr_reader :emojis
40
+ # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Message}] A dictionary of messages.
41
+ attr_reader :messages
42
+ # @return [Discorb::Logger] The logger.
43
+ attr_reader :log
44
+
45
+ #
46
+ # Initializes a new client.
47
+ #
48
+ # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions that the client is using.
49
+ # @param [Discorb::Intents] intents The intents that the client is currently using.
50
+ # @param [Integer] message_caches The number of messages to cache.
51
+ # @param [#puts] log The IO object to use for logging.
52
+ # @param [Boolean] colorize_log Whether to colorize the log.
53
+ # @param [:debug, :info, :warn, :error, :critical] log_level The log level.
54
+ # @param [Boolean] wait_until_ready Whether to delay event dispatch until ready.
55
+ #
56
+ def initialize(allowed_mentions: nil, intents: nil, message_caches: 1000, log: nil, colorize_log: false, log_level: :info, wait_until_ready: true)
57
+ @allowed_mentions = allowed_mentions || AllowedMentions.new(everyone: true, roles: true, users: true)
58
+ @intents = (intents or Intents.default)
59
+ @events = {}
60
+ @api_version = nil
61
+ @log = Logger.new(log, colorize_log, log_level)
62
+ @user = nil
63
+ @users = Discorb::Dictionary.new
64
+ @channels = Discorb::Dictionary.new
65
+ @guilds = Discorb::Dictionary.new(sort: ->(k) { k[0].to_i })
66
+ @emojis = Discorb::Dictionary.new
67
+ @messages = Discorb::Dictionary.new(limit: message_caches)
68
+ @application = nil
69
+ @last_s = nil
70
+ @identify_presence = nil
71
+ @wait_until_ready = wait_until_ready
72
+ @ready = false
73
+ @tasks = []
74
+ @conditions = {}
75
+ end
76
+
77
+ #
78
+ # Registers an event handler.
79
+ # @see file:docs/Events.md
80
+ #
81
+ # @param [Symbol] event_name The name of the event.
82
+ # @param [Symbol] id Custom ID of the event.
83
+ # @param [Hash] discriminator The discriminator of the event.
84
+ # @param [Proc] block The block to execute when the event is triggered.
85
+ #
86
+ # @return [Discorb::Event] The event.
87
+ #
88
+ def on(event_name, id: nil, **discriminator, &block)
89
+ ne = Event.new(block, id, discriminator)
90
+ @events[event_name] ||= []
91
+ @events[event_name] << ne
92
+ ne
93
+ end
94
+
95
+ #
96
+ # Almost same as {#on}, but only triggers the event once.
97
+ #
98
+ # @param (see #on)
99
+ #
100
+ # @return [Discorb::Event] The event.
101
+ #
102
+ def once(event_name, id: nil, **discriminator, &block)
103
+ discriminator[:once] = true
104
+ ne = Event.new(block, id, discriminator)
105
+ @events[event_name] ||= []
106
+ @events[event_name] << ne
107
+ ne
108
+ end
109
+
110
+ #
111
+ # Remove event by ID.
112
+ #
113
+ # @param [Symbol] event_name The name of the event.
114
+ # @param [Symbol] id The ID of the event.
115
+ #
116
+ def remove_event(event_name, id)
117
+ @events[event_name].delete_if { |e| e.id == id }
118
+ end
119
+
120
+ #
121
+ # Dispatch an event.
122
+ #
123
+ # @param [Symbol] event_name The name of the event.
124
+ # @param [Object] args The arguments to pass to the event.
125
+ #
126
+ def dispatch(event_name, *args)
127
+ Async do |_task|
128
+ if (conditions = @conditions[event_name])
129
+ ids = Set[*conditions.map(&:first).map(&:object_id)]
130
+ conditions.delete_if do |condition|
131
+ next unless ids.include?(condition.first.object_id)
132
+
133
+ check_result = condition[1].nil? || condition[1].call(*args)
134
+ if check_result
135
+ condition.first.signal(args)
136
+ true
137
+ else
138
+ false
139
+ end
140
+ end
141
+ end
142
+ if @events[event_name].nil?
143
+ @log.debug "Event #{event_name} doesn't have any proc, skipping"
144
+ next
145
+ end
146
+ @log.debug "Dispatching event #{event_name}"
147
+ @events[event_name].each do |block|
148
+ lambda { |event_args|
149
+ Async(annotation: "Discorb event: #{event_name}") do |task|
150
+ @events[event_name].delete(block) if block.discriminator[:once]
151
+ block.call(task, *event_args)
152
+ @log.debug "Dispatched proc with ID #{block.id.inspect}"
153
+ rescue StandardError, ScriptError => e
154
+ message = "An error occurred while dispatching proc with ID #{block.id.inspect}\n#{e.full_message}"
155
+ dispatch(:error, event_name, event_args, e)
156
+ if @log.out
157
+ @log.error message
158
+ else
159
+ warn message
160
+ end
161
+ end
162
+ }.call(args)
163
+ end
164
+ end
165
+ end
166
+
167
+ #
168
+ # Starts the client.
169
+ #
170
+ # @param [String] token The token to use.
171
+ #
172
+ def run(token)
173
+ @token = token.to_s
174
+ connect_gateway(true)
175
+ end
176
+
177
+ #
178
+ # Fetch user from ID.
179
+ # @macro async
180
+ # @macro http
181
+ #
182
+ # @param [#to_s] id <description>
183
+ #
184
+ # @return [Discorb::User] The user.
185
+ #
186
+ # @raise [Discorb::NotFoundError] If the user doesn't exist.
187
+ #
188
+ def fetch_user(id)
189
+ Async do
190
+ _resp, data = internet.get("/users/#{id}").wait
191
+ User.new(self, data)
192
+ end
193
+ end
194
+
195
+ #
196
+ # Fetch channel from ID.
197
+ # @macro async
198
+ # @macro http
199
+ #
200
+ # @param [#to_s] id The ID of the channel.
201
+ #
202
+ # @return [Discorb::Channel] The channel.
203
+ #
204
+ # @raise [Discorb::NotFoundError] If the channel doesn't exist.
205
+ #
206
+ def fetch_channel(id)
207
+ Async do
208
+ _resp, data = internet.get("/channels/#{id}").wait
209
+ Channel.make_channel(self, data)
210
+ end
211
+ end
212
+
213
+ #
214
+ # Fetch guild from ID.
215
+ # @macro async
216
+ # @macro http
217
+ #
218
+ # @param [#to_s] id <description>
219
+ #
220
+ # @return [Discorb::Guild] The guild.
221
+ #
222
+ # @raise [Discorb::NotFoundError] If the guild doesn't exist.
223
+ #
224
+ def fetch_guild(id)
225
+ Async do
226
+ _resp, data = internet.get("/guilds/#{id}").wait
227
+ Guild.new(self, data, false)
228
+ end
229
+ end
230
+
231
+ #
232
+ # Fetch invite from code.
233
+ # @macro async
234
+ # @macro http
235
+ #
236
+ # @param [String] code The code of the invite.
237
+ # @param [Boolean] with_count Whether to include the count of the invite.
238
+ # @param [Boolean] with_expiration Whether to include the expiration of the invite.
239
+ #
240
+ # @return [Discorb::Invite] The invite.
241
+ #
242
+ def fetch_invite(code, with_count: false, with_expiration: false)
243
+ Async do
244
+ _resp, data = internet.get("/invites/#{code}?with_count=#{with_count}&with_expiration=#{with_expiration}").wait
245
+ Invite.new(self, data, false)
246
+ end
247
+ end
248
+
249
+ #
250
+ # Fetch webhook from ID.
251
+ # If application was cached, it will be used.
252
+ # @macro async
253
+ # @macro http
254
+ #
255
+ # @param [Boolean] force Whether to force the fetch.
256
+ #
257
+ # @return [Discorb::Application] The application.
258
+ #
259
+ def fetch_application(force: false)
260
+ Async do
261
+ next @application if @application && !force
262
+
263
+ _resp, data = internet.get("/oauth2/applications/@me").wait
264
+ @application = Application.new(self, data)
265
+ @application
266
+ end
267
+ end
268
+
269
+ #
270
+ # Fetch nitro sticker pack from ID.
271
+ # @macro async
272
+ # @macro http
273
+ #
274
+ # @return [Array<Discorb::Sticker::Pack>] The packs.
275
+ #
276
+ def fetch_nitro_sticker_packs
277
+ Async do
278
+ _resp, data = internet.get("/stickers-packs").wait
279
+ data.map { |pack| Sticker::Pack.new(self, pack) }
280
+ end
281
+ end
282
+
283
+ #
284
+ # Update presence of the client.
285
+ #
286
+ # @param [Discorb::Activity] activity The activity to update.
287
+ # @param [:online, :idle, :dnd, :invisible] status The status to update.
288
+ #
289
+ def update_presence(activity = nil, status: nil)
290
+ payload = {}
291
+ if !activity.nil?
292
+ payload[:activities] = [activity.to_hash]
293
+ end
294
+ payload[:status] = status unless status.nil?
295
+ if @connection
296
+ Async do |_task|
297
+ send_gateway(3, **payload)
298
+ end
299
+ else
300
+ @identify_presence = payload
301
+ end
302
+ end
303
+
304
+ alias change_presence update_presence
305
+
306
+ #
307
+ # Method to wait for a event.
308
+ #
309
+ # @param [Symbol] event_name The name of the event.
310
+ # @param [Integer] timeout The timeout in seconds.
311
+ # @param [Proc] check The check to use.
312
+ #
313
+ # @return [Object] The result of the event.
314
+ #
315
+ # @raise [Discorb::TimeoutError] If the event didn't occur in time.
316
+ #
317
+ def event_lock(event, timeout = nil, &check)
318
+ Async do |task|
319
+ condition = Async::Condition.new
320
+ @conditions[event] ||= []
321
+ @conditions[event] << [condition, check]
322
+ if timeout.nil?
323
+ value = condition.wait
324
+ else
325
+ timeout_task = task.with_timeout(timeout) do
326
+ condition.wait
327
+ rescue Async::TimeoutError
328
+ @conditions[event].delete_if { |c| c.first == condition }
329
+ raise Discorb::TimeoutError, "Timeout waiting for event #{event}", cause: nil
330
+ end
331
+ value = timeout_task
332
+ end
333
+ value.length <= 1 ? value.first : value
334
+ end
335
+ end
336
+
337
+ alias await event_lock
338
+
339
+ def inspect
340
+ "#<#{self.class} user=\"#{user}\">"
341
+ end
342
+
343
+ #
344
+ # Load the extension.
345
+ #
346
+ # @param [Module] mod The extension to load.
347
+ #
348
+ def extend(mod)
349
+ if mod.respond_to?(:events)
350
+ mod.events.each do |name, events|
351
+ @events[name] = [] if @events[name].nil?
352
+ events.each do |event|
353
+ @events[name] << event
354
+ end
355
+ end
356
+ mod.client = self
357
+ end
358
+ super(mod)
359
+ end
360
+
361
+ include Discorb::GatewayHandler
362
+ end
363
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Represents RGB color.
6
+ #
7
+ class Color
8
+ attr_accessor :value
9
+
10
+ @discord_colors = {
11
+ teal: 0x1abc9c,
12
+ dark_teal: 0x11806a,
13
+ green: 0x2ecc71,
14
+ dark_green: 0x1f8b4c,
15
+ blue: 0x3498db,
16
+ dark_blue: 0x206694,
17
+ purple: 0x9b59b6,
18
+ dark_purple: 0x71368a,
19
+ magenta: 0xe91e63,
20
+ dark_magenta: 0xad1457,
21
+ gold: 0xf1c40f,
22
+ dark_gold: 0xc27c0e,
23
+ orange: 0xe67e22,
24
+ dark_orange: 0xa84300,
25
+ red: 0xe74c3c,
26
+ dark_red: 0x992d22,
27
+ lighter_grey: 0x95a5a6,
28
+ lighter_gray: 0x95a5a6,
29
+ dark_grey: 0x607d8b,
30
+ dark_gray: 0x607d8b,
31
+ light_grey: 0x979c9f,
32
+ light_gray: 0x979c9f,
33
+ darker_grey: 0x546e7a,
34
+ darker_gray: 0x546e7a,
35
+ og_blurple: 0x7289da,
36
+ blurple: 0x5865f2,
37
+ greyple: 0x99aab5,
38
+ dark_theme: 0x36393f,
39
+ fuchsia: 0xeb459e,
40
+ yellow: 0xfee75c,
41
+ }.freeze
42
+
43
+ #
44
+ # Create a color from a Integer.
45
+ #
46
+ # @param [Integer] value A color value.
47
+ #
48
+ def initialize(value)
49
+ @value = value
50
+ end
51
+
52
+ #
53
+ # Integerize a color.
54
+ #
55
+ # @return [Integer] A color value.
56
+ #
57
+ def to_i
58
+ @value
59
+ end
60
+
61
+ #
62
+ # Convert a color to a hexadecimal value.
63
+ #
64
+ # @return [String] A hexadecimal value.
65
+ #
66
+ def to_hex
67
+ @value.to_s(16).rjust(6, "0")
68
+ end
69
+
70
+ #
71
+ # Convert a color to RGB array.
72
+ #
73
+ # @return [Array(Integer, Integer, Integer)] A RGB array.
74
+ #
75
+ def to_rgb
76
+ [@value / (256 * 256), @value / 256 % 256, @value % 256]
77
+ end
78
+
79
+ alias to_a to_rgb
80
+ alias deconstruct to_rgb
81
+
82
+ #
83
+ # Convert a color to RGB hash.
84
+ #
85
+ # @return [Hash{:r, :g, :b => Integer}] A RGB hash.
86
+ #
87
+ def to_rgb_hash
88
+ [@value / (256 * 256), @value / 256 % 256, @value % 256]
89
+ end
90
+
91
+ alias deconstruct_keys to_rgb_hash
92
+
93
+ #
94
+ # Converts a color to a `#000000` string.
95
+ #
96
+ # @return [String] Converted string.
97
+ #
98
+ def to_s
99
+ "##{to_hex}"
100
+ end
101
+
102
+ def inspect
103
+ "#<#{self.class} #{@value}/#{self}>"
104
+ end
105
+
106
+ #
107
+ # Create a color from a hexadecimal string.
108
+ #
109
+ # @param [String] hex A hexadecimal string.
110
+ #
111
+ # @return [Discorb::Color] A color object.
112
+ #
113
+ def self.from_hex(hex)
114
+ new(hex.to_i(16))
115
+ end
116
+
117
+ #
118
+ # Create a color from a RGB array.
119
+ #
120
+ # @param [Integer] red A red value.
121
+ # @param [Integer] green A green value.
122
+ # @param [Integer] blue A blue value.
123
+ #
124
+ # @return [Discorb::Color] A color object.
125
+ #
126
+ def self.from_rgb(red, green, blue)
127
+ new(red * 256 * 256 + green * 256 + blue)
128
+ end
129
+
130
+ #
131
+ # Create a color from a Discord's color.
132
+ # Currently these colors are supported:
133
+ # - teal (0x1abc9c)
134
+ # - dark_teal (0x11806a)
135
+ # - green (0x2ecc71)
136
+ # - dark_green (0x1f8b4c)
137
+ # - blue (0x3498db)
138
+ # - dark_blue (0x206694)
139
+ # - purple (0x9b59b6)
140
+ # - dark_purple (0x71368a)
141
+ # - magenta (0xe91e63)
142
+ # - dark_magenta (0xad1457)
143
+ # - gold (0xf1c40f)
144
+ # - dark_gold (0xc27c0e)
145
+ # - orange (0xe67e22)
146
+ # - dark_orange (0xa84300)
147
+ # - red (0xe74c3c)
148
+ # - dark_red (0x992d22)
149
+ # - lighter_grey (0x95a5a6)
150
+ # - lighter_gray (0x95a5a6)
151
+ # - dark_grey (0x607d8b)
152
+ # - dark_gray (0x607d8b)
153
+ # - light_grey (0x979c9f)
154
+ # - light_gray (0x979c9f)
155
+ # - darker_grey (0x546e7a)
156
+ # - darker_gray (0x546e7a)
157
+ # - og_blurple (0x7289da)
158
+ # - blurple (0x5865f2)
159
+ # - greyple (0x99aab5)
160
+ # - dark_theme (0x36393f)
161
+ # - fuchsia (0xeb459e)
162
+ #
163
+ # @param [Symbol] color A Discord color name.
164
+ #
165
+ # @return [Discorb::Color] A color object.
166
+ #
167
+ def self.[](color)
168
+ new(@discord_colors[color])
169
+ end
170
+ end
171
+
172
+ Colour = Color
173
+ end