discorb 0.0.1

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 (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