discordrb 3.4.0 → 3.5.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +44 -18
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
  5. data/.github/workflows/codeql.yml +65 -0
  6. data/.markdownlint.json +4 -0
  7. data/.rubocop.yml +8 -2
  8. data/CHANGELOG.md +419 -222
  9. data/LICENSE.txt +1 -1
  10. data/README.md +37 -25
  11. data/discordrb-webhooks.gemspec +4 -1
  12. data/discordrb.gemspec +9 -6
  13. data/lib/discordrb/api/application.rb +202 -0
  14. data/lib/discordrb/api/channel.rb +182 -11
  15. data/lib/discordrb/api/interaction.rb +54 -0
  16. data/lib/discordrb/api/invite.rb +2 -2
  17. data/lib/discordrb/api/server.rb +42 -19
  18. data/lib/discordrb/api/user.rb +9 -3
  19. data/lib/discordrb/api/webhook.rb +57 -0
  20. data/lib/discordrb/api.rb +19 -5
  21. data/lib/discordrb/bot.rb +328 -33
  22. data/lib/discordrb/cache.rb +27 -22
  23. data/lib/discordrb/commands/command_bot.rb +14 -7
  24. data/lib/discordrb/commands/container.rb +1 -1
  25. data/lib/discordrb/commands/parser.rb +2 -2
  26. data/lib/discordrb/commands/rate_limiter.rb +1 -1
  27. data/lib/discordrb/container.rb +132 -3
  28. data/lib/discordrb/data/activity.rb +8 -1
  29. data/lib/discordrb/data/attachment.rb +15 -0
  30. data/lib/discordrb/data/audit_logs.rb +3 -3
  31. data/lib/discordrb/data/channel.rb +167 -23
  32. data/lib/discordrb/data/component.rb +229 -0
  33. data/lib/discordrb/data/integration.rb +42 -3
  34. data/lib/discordrb/data/interaction.rb +800 -0
  35. data/lib/discordrb/data/invite.rb +2 -2
  36. data/lib/discordrb/data/member.rb +108 -33
  37. data/lib/discordrb/data/message.rb +100 -20
  38. data/lib/discordrb/data/overwrite.rb +13 -7
  39. data/lib/discordrb/data/role.rb +58 -1
  40. data/lib/discordrb/data/server.rb +82 -80
  41. data/lib/discordrb/data/user.rb +69 -9
  42. data/lib/discordrb/data/webhook.rb +97 -4
  43. data/lib/discordrb/data.rb +3 -0
  44. data/lib/discordrb/errors.rb +44 -3
  45. data/lib/discordrb/events/channels.rb +1 -1
  46. data/lib/discordrb/events/interactions.rb +482 -0
  47. data/lib/discordrb/events/message.rb +9 -6
  48. data/lib/discordrb/events/presence.rb +21 -14
  49. data/lib/discordrb/events/reactions.rb +0 -1
  50. data/lib/discordrb/events/threads.rb +96 -0
  51. data/lib/discordrb/gateway.rb +30 -17
  52. data/lib/discordrb/permissions.rb +59 -34
  53. data/lib/discordrb/version.rb +1 -1
  54. data/lib/discordrb/voice/encoder.rb +13 -4
  55. data/lib/discordrb/voice/network.rb +18 -7
  56. data/lib/discordrb/voice/sodium.rb +3 -1
  57. data/lib/discordrb/voice/voice_bot.rb +3 -3
  58. data/lib/discordrb/webhooks.rb +2 -0
  59. data/lib/discordrb.rb +37 -4
  60. metadata +53 -19
  61. data/.codeclimate.yml +0 -16
  62. data/.travis.yml +0 -32
  63. data/bin/travis_build_docs.sh +0 -17
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb::Events
4
+ # Raised when a thread is created
5
+ class ThreadCreateEvent < Event
6
+ # @return [Channel] the thread in question.
7
+ attr_reader :thread
8
+
9
+ delegate :name, :server, :owner, :parent_channel, :thread_metadata, to: :thread
10
+
11
+ def initialize(data, bot)
12
+ @bot = bot
13
+ @thread = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
14
+ end
15
+ end
16
+
17
+ # Event handler for ChannelCreateEvent
18
+ class ThreadCreateEventHandler < EventHandler
19
+ def matches?(event)
20
+ # Check for the proper event type
21
+ return false unless event.is_a? ThreadCreateEvent
22
+
23
+ [
24
+ matches_all(@attributes[:name], event.name) do |a, e|
25
+ a == if a.is_a? String
26
+ e.to_s
27
+ else
28
+ e
29
+ end
30
+ end,
31
+ matches_all(@attributes[:server], event.server) do |a, e|
32
+ a.resolve_id == e.resolve_id
33
+ end,
34
+ matches_all(@attributes[:invitable], event.thread.invitable) do |a, e|
35
+ a == e
36
+ end,
37
+ matches_all(@attributes[:owner], event.thread.owner) do |a, e|
38
+ a.resolve_id == e.resolve_id
39
+ end,
40
+ matches_all(@attributes[:channel], event.thread.parent) do |a, e|
41
+ a.resolve_id == e.resolve_id
42
+ end
43
+ ].reduce(true, &:&)
44
+ end
45
+ end
46
+
47
+ # Raised when a thread is updated (e.g. name changes)
48
+ class ThreadUpdateEvent < ThreadCreateEvent; end
49
+
50
+ # Event handler for ThreadUpdateEvent
51
+ class ThreadUpdateEventHandler < ThreadCreateEventHandler
52
+ def matches?(event)
53
+ # Check for the proper event type
54
+ return false unless event.is_a? ThreadUpdateEvent
55
+
56
+ super
57
+ end
58
+ end
59
+
60
+ # Raised when members are added or removed from a thread.
61
+ class ThreadMembersUpdateEvent < Event
62
+ # @return [Channel]
63
+ attr_reader :thread
64
+
65
+ # @return [Array<Member, User>]
66
+ attr_reader :added_members
67
+
68
+ # @return [Array<Integer>]
69
+ attr_reader :removed_member_ids
70
+
71
+ # @return [Integer]
72
+ attr_reader :member_count
73
+
74
+ delegate :name, :server, :owner, :parent_channel, :thread_metadata, to: :thread
75
+
76
+ def initialize(data, bot)
77
+ @bot = bot
78
+ @thread = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
79
+ @added_members = data['added_members']&.map do |member|
80
+ data['guild_id'] ? bot.member(data['guild_id'], member['user_id']) : bot.user(member['user_id'])
81
+ end || []
82
+ @removed_member_ids = data['removed_member_ids']&.map(&:resolve_id) || []
83
+ @member_count = data['member_count']
84
+ end
85
+ end
86
+
87
+ # Event handler for ThreadMembersUpdateEvent
88
+ class ThreadMembersUpdateEventHandler < ThreadCreateEventHandler
89
+ def matches?(event)
90
+ # Check for the proper event type
91
+ return false unless event.is_a? ThreadMembersUpdateEvent
92
+
93
+ super
94
+ end
95
+ end
96
+ end
@@ -134,7 +134,7 @@ module Discordrb
134
134
  LARGE_THRESHOLD = 100
135
135
 
136
136
  # The version of the gateway that's supposed to be used.
137
- GATEWAY_VERSION = 6
137
+ GATEWAY_VERSION = 9
138
138
 
139
139
  # Heartbeat ACKs are Discord's way of verifying on the client side whether the connection is still alive. If this is
140
140
  # set to true (default value) the gateway client will use that functionality to detect zombie connections and
@@ -143,7 +143,10 @@ module Discordrb
143
143
  # @return [true, false] whether or not this gateway should check for heartbeat ACKs.
144
144
  attr_accessor :check_heartbeat_acks
145
145
 
146
- def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = nil)
146
+ # @return [Integer] the intent parameter sent to the gateway server.
147
+ attr_reader :intents
148
+
149
+ def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS)
147
150
  @token = token
148
151
  @bot = bot
149
152
 
@@ -277,12 +280,12 @@ module Discordrb
277
280
  def identify
278
281
  compress = @compress_mode == :large
279
282
  send_identify(@token, {
280
- '$os': RUBY_PLATFORM,
281
- '$browser': 'discordrb',
282
- '$device': 'discordrb',
283
- '$referrer': '',
284
- '$referring_domain': ''
285
- }, compress, 100, @shard_key)
283
+ os: RUBY_PLATFORM,
284
+ browser: 'discordrb',
285
+ device: 'discordrb',
286
+ referrer: '',
287
+ referring_domain: ''
288
+ }, compress, LARGE_THRESHOLD, @shard_key, @intents)
286
289
  end
287
290
 
288
291
  # Sends an identify packet (op 2). This starts a new session on the current connection and tells Discord who we are.
@@ -292,26 +295,26 @@ module Discordrb
292
295
  # @param properties [Hash<Symbol => String>] A list of properties for Discord to use in analytics. The following
293
296
  # keys are recognised:
294
297
  #
295
- # - "$os" (recommended value: the operating system the bot is running on)
296
- # - "$browser" (recommended value: library name)
297
- # - "$device" (recommended value: library name)
298
- # - "$referrer" (recommended value: empty)
299
- # - "$referring_domain" (recommended value: empty)
298
+ # - "os" (recommended value: the operating system the bot is running on)
299
+ # - "browser" (recommended value: library name)
300
+ # - "device" (recommended value: library name)
301
+ # - "referrer" (recommended value: empty)
302
+ # - "referring_domain" (recommended value: empty)
300
303
  #
301
304
  # @param compress [true, false] Whether certain large packets should be compressed using zlib.
302
305
  # @param large_threshold [Integer] The member threshold after which a server counts as large and will have to have
303
306
  # its member list chunked.
304
307
  # @param shard_key [Array(Integer, Integer), nil] The shard key to use for sharding, represented as
305
308
  # [shard_id, num_shards], or nil if the bot should not be sharded.
306
- def send_identify(token, properties, compress, large_threshold, shard_key = nil)
309
+ def send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS)
307
310
  data = {
308
311
  # Don't send a v anymore as it's entirely determined by the URL now
309
312
  token: token,
310
313
  properties: properties,
311
314
  compress: compress,
312
- large_threshold: large_threshold
315
+ large_threshold: large_threshold,
316
+ intents: intents
313
317
  }
314
- data[:intents] = @intents unless @intents.nil?
315
318
 
316
319
  # Don't include the shard key at all if it is nil as Discord checks for its mere existence
317
320
  data[:shard] = shard_key if shard_key
@@ -790,7 +793,8 @@ module Discordrb
790
793
  # - 4003: Not authenticated. How did this happen?
791
794
  # - 4004: Authentication failed. Token was wrong, nothing we can do.
792
795
  # - 4011: Sharding required. Currently requires developer intervention.
793
- FATAL_CLOSE_CODES = [4003, 4004, 4011].freeze
796
+ # - 4014: Use of disabled privileged intents.
797
+ FATAL_CLOSE_CODES = [4003, 4004, 4011, 4014].freeze
794
798
 
795
799
  def handle_close(e)
796
800
  @bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot))
@@ -800,6 +804,15 @@ module Discordrb
800
804
  LOGGER.error('Websocket close frame received!')
801
805
  LOGGER.error("Code: #{e.code}")
802
806
  LOGGER.error("Message: #{e.data}")
807
+
808
+ if e.code == 4014
809
+ LOGGER.error(<<~ERROR)
810
+ You attempted to identify with privileged intents that your bot is not authorized to use
811
+ Please enable the privileged intents on the bot page of your application on the discord developer page.
812
+ Read more here https://discord.com/developers/docs/topics/gateway#privileged-intents
813
+ ERROR
814
+ end
815
+
803
816
  @should_reconnect = false if FATAL_CLOSE_CODES.include?(e.code)
804
817
  elsif e.is_a? Exception
805
818
  # Log the exception
@@ -6,37 +6,47 @@ module Discordrb
6
6
  # This hash maps bit positions to logical permissions.
7
7
  FLAGS = {
8
8
  # Bit => Permission # Value
9
- 0 => :create_instant_invite, # 1
10
- 1 => :kick_members, # 2
11
- 2 => :ban_members, # 4
12
- 3 => :administrator, # 8
13
- 4 => :manage_channels, # 16
14
- 5 => :manage_server, # 32
15
- 6 => :add_reactions, # 64
16
- 7 => :view_audit_log, # 128
17
- 8 => :priority_speaker, # 256
18
- 9 => :stream, # 512
19
- 10 => :read_messages, # 1024
20
- 11 => :send_messages, # 2048
21
- 12 => :send_tts_messages, # 4096
22
- 13 => :manage_messages, # 8192
23
- 14 => :embed_links, # 16384
24
- 15 => :attach_files, # 32768
25
- 16 => :read_message_history, # 65536
26
- 17 => :mention_everyone, # 131072
27
- 18 => :use_external_emoji, # 262144
28
- 19 => :view_server_insights, # 524288
29
- 20 => :connect, # 1048576
30
- 21 => :speak, # 2097152
31
- 22 => :mute_members, # 4194304
32
- 23 => :deafen_members, # 8388608
33
- 24 => :move_members, # 16777216
34
- 25 => :use_voice_activity, # 33554432
35
- 26 => :change_nickname, # 67108864
36
- 27 => :manage_nicknames, # 134217728
37
- 28 => :manage_roles, # 268435456, also Manage Permissions
38
- 29 => :manage_webhooks, # 536870912
39
- 30 => :manage_emojis # 1073741824
9
+ 0 => :create_instant_invite, # 1
10
+ 1 => :kick_members, # 2
11
+ 2 => :ban_members, # 4
12
+ 3 => :administrator, # 8
13
+ 4 => :manage_channels, # 16
14
+ 5 => :manage_server, # 32
15
+ 6 => :add_reactions, # 64
16
+ 7 => :view_audit_log, # 128
17
+ 8 => :priority_speaker, # 256
18
+ 9 => :stream, # 512
19
+ 10 => :read_messages, # 1024
20
+ 11 => :send_messages, # 2048
21
+ 12 => :send_tts_messages, # 4096
22
+ 13 => :manage_messages, # 8192
23
+ 14 => :embed_links, # 16384
24
+ 15 => :attach_files, # 32768
25
+ 16 => :read_message_history, # 65536
26
+ 17 => :mention_everyone, # 131072
27
+ 18 => :use_external_emoji, # 262144
28
+ 19 => :view_server_insights, # 524288
29
+ 20 => :connect, # 1048576
30
+ 21 => :speak, # 2097152
31
+ 22 => :mute_members, # 4194304
32
+ 23 => :deafen_members, # 8388608
33
+ 24 => :move_members, # 16777216
34
+ 25 => :use_voice_activity, # 33554432
35
+ 26 => :change_nickname, # 67108864
36
+ 27 => :manage_nicknames, # 134217728
37
+ 28 => :manage_roles, # 268435456, also Manage Permissions
38
+ 29 => :manage_webhooks, # 536870912
39
+ 30 => :manage_emojis, # 1073741824, also Manage Stickers
40
+ 31 => :use_slash_commands, # 2147483648
41
+ 32 => :request_to_speak, # 4294967296
42
+ 33 => :manage_events, # 8589934592
43
+ 34 => :manage_threads, # 17179869184
44
+ 35 => :use_public_threads, # 34359738368
45
+ 36 => :use_private_threads, # 68719476736
46
+ 37 => :use_external_stickers, # 137438953472
47
+ 38 => :send_messages_in_threads, # 274877906944
48
+ 39 => :use_embedded_activities, # 549755813888
49
+ 40 => :moderate_members # 1099511627776
40
50
  }.freeze
41
51
 
42
52
  FLAGS.each do |position, flag|
@@ -101,7 +111,7 @@ module Discordrb
101
111
  # permission.can_speak = true
102
112
  # @example Create a permissions object that could allow/deny read messages, connect, and speak by an array of symbols
103
113
  # Permissions.new [:read_messages, :connect, :speak]
104
- # @param bits [Integer, Array<Symbol>] The permission bits that should be set from the beginning, or an array of permission flag symbols
114
+ # @param bits [String, Integer, Array<Symbol>] The permission bits that should be set from the beginning, or an array of permission flag symbols
105
115
  # @param writer [RoleWriter] The writer that should be used to update data when a permission is set.
106
116
  def initialize(bits = 0, writer = nil)
107
117
  @writer = writer
@@ -109,12 +119,21 @@ module Discordrb
109
119
  @bits = if bits.is_a? Array
110
120
  self.class.bits(bits)
111
121
  else
112
- bits
122
+ bits.to_i
113
123
  end
114
124
 
115
125
  init_vars
116
126
  end
117
127
 
128
+ # Return an array of permission flag symbols for this class's permissions
129
+ # @example Get the permissions for the bits "9"
130
+ # permissions = Permissions.new(9)
131
+ # permissions.defined_permissions #=> [:create_instant_invite, :administrator]
132
+ # @return [Array<Symbol>] the permissions
133
+ def defined_permissions
134
+ FLAGS.filter_map { |value, name| (@bits & (1 << value)).positive? ? name : nil }
135
+ end
136
+
118
137
  # Comparison based on permission bits
119
138
  def ==(other)
120
139
  false unless other.is_a? Discordrb::Permissions
@@ -154,6 +173,12 @@ module Discordrb
154
173
  # has_manage_channels = member.defined_permission?(:manage_channels)
155
174
  # @return [true, false] whether or not this user has the permission defined.
156
175
  def defined_permission?(action, channel = nil)
176
+ # For slash commands we may not have access to the server or role
177
+ # permissions. In this case we use the permissions given to us by the
178
+ # interaction. If attempting to check against a specific channel the check
179
+ # is skipped.
180
+ return @permissions.__send__(action) if @permissions && channel.nil?
181
+
157
182
  # Get the permission the user's roles have
158
183
  role_permission = defined_role_permission?(action, channel)
159
184
 
@@ -179,7 +204,7 @@ module Discordrb
179
204
  private
180
205
 
181
206
  def defined_role_permission?(action, channel)
182
- roles_to_check = [@server.everyone_role] + @roles
207
+ roles_to_check = [@server.everyone_role] + roles
183
208
 
184
209
  # For each role, check if
185
210
  # (1) the channel explicitly allows or permits an action for the role and
@@ -3,5 +3,5 @@
3
3
  # Discordrb and all its functionality, in this case only the version.
4
4
  module Discordrb
5
5
  # The current version of discordrb.
6
- VERSION = '3.4.0'
6
+ VERSION = '3.5.0'
7
7
  end
@@ -77,7 +77,7 @@ module Discordrb::Voice
77
77
  # @param options [String] ffmpeg options to pass after the -i flag
78
78
  # @return [IO] the audio, encoded as s16le PCM
79
79
  def encode_file(file, options = '')
80
- command = "#{ffmpeg_command} -loglevel 0 -i \"#{file}\" #{options} -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
80
+ command = ffmpeg_command(input: file, options: options)
81
81
  IO.popen(command)
82
82
  end
83
83
 
@@ -87,14 +87,23 @@ module Discordrb::Voice
87
87
  # @param options [String] ffmpeg options to pass after the -i flag
88
88
  # @return [IO] the audio, encoded as s16le PCM
89
89
  def encode_io(io, options = '')
90
- command = "#{ffmpeg_command} -loglevel 0 -i - #{options} -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
90
+ command = ffmpeg_command(options: options)
91
91
  IO.popen(command, in: io)
92
92
  end
93
93
 
94
94
  private
95
95
 
96
- def ffmpeg_command
97
- @use_avconv ? 'avconv' : 'ffmpeg'
96
+ def ffmpeg_command(input: '-', options: null)
97
+ [
98
+ @use_avconv ? 'avconv' : 'ffmpeg',
99
+ '-loglevel', '0',
100
+ '-i', input,
101
+ '-f', 's16le',
102
+ '-ar', '48000',
103
+ '-ac', '2',
104
+ 'pipe:1',
105
+ filter_volume_argument
106
+ ].concat(options.split).reject { |segment| segment.nil? || segment == '' }
98
107
  end
99
108
 
100
109
  def filter_volume_argument
@@ -42,10 +42,10 @@ module Discordrb::Voice
42
42
  attr_writer :secret_key
43
43
 
44
44
  # The UDP encryption mode
45
- attr_reader :mode # rubocop:disable Style/BisectedAttrAccessor
45
+ attr_reader :mode
46
46
 
47
47
  # @!visibility private
48
- attr_writer :mode # rubocop:disable Style/BisectedAttrAccessor
48
+ attr_writer :mode
49
49
 
50
50
  # Creates a new UDP connection. Only creates a socket as the discovery reply may come before the data is
51
51
  # initialized.
@@ -69,9 +69,9 @@ module Discordrb::Voice
69
69
  # @return [Array(String, Integer)] the IP and port received from the discovery reply.
70
70
  def receive_discovery_reply
71
71
  # Wait for a UDP message
72
- message = @socket.recv(70)
73
- ip = message[4..-3].delete("\0")
74
- port = message[-2..-1].unpack1('n')
72
+ message = @socket.recv(74)
73
+ ip = message[8..-3].delete("\0")
74
+ port = message[-2..].unpack1('n')
75
75
  [ip, port]
76
76
  end
77
77
 
@@ -98,10 +98,21 @@ module Discordrb::Voice
98
98
  # Sends the UDP discovery packet with the internally stored SSRC. Discord will send a reply afterwards which can
99
99
  # be received using {#receive_discovery_reply}
100
100
  def send_discovery
101
- discovery_packet = [@ssrc].pack('N')
101
+ # Create empty packet
102
+ discovery_packet = ''
102
103
 
103
- # Add 66 zeroes so the packet is 70 bytes long
104
+ # Add Type request (0x1 = request, 0x2 = response)
105
+ discovery_packet += [0x1].pack('n')
106
+
107
+ # Add Length (excluding Type and itself = 70)
108
+ discovery_packet += [70].pack('n')
109
+
110
+ # Add SSRC
111
+ discovery_packet += [@ssrc].pack('N')
112
+
113
+ # Add 66 zeroes so the packet is 74 bytes long
104
114
  discovery_packet += "\0" * 66
115
+
105
116
  send_packet(discovery_packet)
106
117
  end
107
118
 
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ffi'
4
+
3
5
  module Discordrb::Voice
4
6
  # @!visibility private
5
7
  module Sodium
6
- extend FFI::Library
8
+ extend ::FFI::Library
7
9
 
8
10
  ffi_lib(['sodium', 'libsodium.so.18', 'libsodium.so.23'])
9
11
 
@@ -22,10 +22,10 @@ module Discordrb::Voice
22
22
  # {VoiceBot#adjust_offset}, and {VoiceBot#adjust_average}.
23
23
  class VoiceBot
24
24
  # @return [Channel] the current voice channel
25
- attr_reader :channel # rubocop:disable Style/BisectedAttrAccessor
25
+ attr_reader :channel
26
26
 
27
27
  # @!visibility private
28
- attr_writer :channel # rubocop:disable Style/BisectedAttrAccessor
28
+ attr_writer :channel
29
29
 
30
30
  # @return [Integer, nil] the amount of time the stream has been playing, or `nil` if nothing has been played yet.
31
31
  attr_reader :stream_time
@@ -149,7 +149,7 @@ module Discordrb::Voice
149
149
 
150
150
  # Sets whether or not the bot is speaking (green circle around user).
151
151
  # @param value [true, false, Integer] whether or not the bot should be speaking, or a bitmask denoting the audio type
152
- # @note https://discordapp.com/developers/docs/topics/voice-connections#speaking for information on the speaking bitmask
152
+ # @note https://discord.com/developers/docs/topics/voice-connections#speaking for information on the speaking bitmask
153
153
  def speaking=(value)
154
154
  @playing = value
155
155
  @ws.send_speaking(value)
@@ -4,6 +4,8 @@ require 'discordrb/webhooks/version'
4
4
  require 'discordrb/webhooks/embeds'
5
5
  require 'discordrb/webhooks/client'
6
6
  require 'discordrb/webhooks/builder'
7
+ require 'discordrb/webhooks/view'
8
+ require 'discordrb/webhooks/modal'
7
9
 
8
10
  module Discordrb
9
11
  # Webhook client
data/lib/discordrb.rb CHANGED
@@ -10,13 +10,13 @@ module Discordrb
10
10
  Thread.current[:discordrb_name] = 'main'
11
11
 
12
12
  # The default debug logger used by discordrb.
13
- LOGGER = Logger.new(ENV['DISCORDRB_FANCY_LOG'])
13
+ LOGGER = Logger.new(ENV.fetch('DISCORDRB_FANCY_LOG', false))
14
14
 
15
15
  # The Unix timestamp Discord IDs are based on
16
16
  DISCORD_EPOCH = 1_420_070_400_000
17
17
 
18
18
  # Used to declare what events you wish to recieve from Discord.
19
- # @see https://discordapp.com/developers/docs/topics/gateway#gateway-intents
19
+ # @see https://discord.com/developers/docs/topics/gateway#gateway-intents
20
20
  INTENTS = {
21
21
  servers: 1 << 0,
22
22
  server_members: 1 << 1,
@@ -35,9 +35,16 @@ module Discordrb
35
35
  direct_message_typing: 1 << 14
36
36
  }.freeze
37
37
 
38
- # @return [Integer] All available intents
38
+ # All available intents
39
39
  ALL_INTENTS = INTENTS.values.reduce(&:|)
40
40
 
41
+ # All unprivileged intents
42
+ # @see https://discord.com/developers/docs/topics/gateway#privileged-intents
43
+ UNPRIVILEGED_INTENTS = ALL_INTENTS & ~(INTENTS[:server_members] | INTENTS[:server_presences])
44
+
45
+ # No intents
46
+ NO_INTENTS = 0
47
+
41
48
  # Compares two objects based on IDs - either the objects' IDs are equal, or one object is equal to the other's ID.
42
49
  def self.id_compare(one_id, other)
43
50
  other.respond_to?(:resolve_id) ? (one_id.resolve_id == other.resolve_id) : (one_id == other)
@@ -46,6 +53,18 @@ module Discordrb
46
53
  # The maximum length a Discord message can have
47
54
  CHARACTER_LIMIT = 2000
48
55
 
56
+ # For creating timestamps with {timestamp}
57
+ # @see https://discord.com/developers/docs/reference#message-formatting-timestamp-styles
58
+ TIMESTAMP_STYLES = {
59
+ short_time: 't', # 16:20
60
+ long_time: 'T', # 16:20:30
61
+ short_date: 'd', # 20/04/2021
62
+ long_date: 'D', # 20 April 2021
63
+ short_datetime: 'f', # 20 April 2021 16:20
64
+ long_datetime: 'F', # Tuesday, 20 April 2021 16:20
65
+ relative: 'R' # 2 months ago
66
+ }.freeze
67
+
49
68
  # Splits a message into chunks of 2000 characters. Attempts to split by lines if possible.
50
69
  # @param msg [String] The message to split.
51
70
  # @return [Array<String>] the message split into chunks
@@ -76,7 +95,7 @@ module Discordrb
76
95
  ideal_ary = ideal.length > CHARACTER_LIMIT ? ideal.split(/(.{1,#{CHARACTER_LIMIT}}\b|.{1,#{CHARACTER_LIMIT}})/o).reject(&:empty?) : [ideal]
77
96
 
78
97
  # Slice off the ideal part and strip newlines
79
- rest = msg[ideal.length..-1].strip
98
+ rest = msg[ideal.length..].strip
80
99
 
81
100
  # If none remains, return an empty array -> we're done
82
101
  return [] unless rest
@@ -84,6 +103,20 @@ module Discordrb
84
103
  # Otherwise, call the method recursively to split the rest of the string and add it onto the ideal array
85
104
  ideal_ary + split_message(rest)
86
105
  end
106
+
107
+ # @param time [Time, Integer] The time to create the timestamp from, or a unix timestamp integer.
108
+ # @param style [Symbol, String] One of the keys from {TIMESTAMP_STYLES} or a string with the style.
109
+ # @return [String]
110
+ # @example
111
+ # Discordrb.timestamp(Time.now, :short_time)
112
+ # # => "<t:1632146954:t>"
113
+ def self.timestamp(time, style = nil)
114
+ if style.nil?
115
+ "<t:#{time.to_i}>"
116
+ else
117
+ "<t:#{time.to_i}:#{TIMESTAMP_STYLES[style] || style}>"
118
+ end
119
+ end
87
120
  end
88
121
 
89
122
  # In discordrb, Integer and {String} are monkey-patched to allow for easy resolution of IDs