discordrb 3.4.3 → 3.6.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
  7. data/.github/workflows/ci.yml +78 -0
  8. data/.github/workflows/codeql.yml +65 -0
  9. data/.github/workflows/deploy.yml +54 -0
  10. data/.github/workflows/release.yml +45 -0
  11. data/.markdownlint.json +4 -0
  12. data/.rubocop.yml +58 -2
  13. data/CHANGELOG.md +485 -225
  14. data/LICENSE.txt +1 -1
  15. data/README.md +38 -26
  16. data/discordrb-webhooks.gemspec +4 -1
  17. data/discordrb.gemspec +18 -10
  18. data/lib/discordrb/api/application.rb +278 -0
  19. data/lib/discordrb/api/channel.rb +222 -18
  20. data/lib/discordrb/api/interaction.rb +63 -0
  21. data/lib/discordrb/api/invite.rb +2 -2
  22. data/lib/discordrb/api/server.rb +123 -66
  23. data/lib/discordrb/api/user.rb +20 -5
  24. data/lib/discordrb/api/webhook.rb +72 -0
  25. data/lib/discordrb/api.rb +35 -25
  26. data/lib/discordrb/bot.rb +437 -66
  27. data/lib/discordrb/cache.rb +41 -22
  28. data/lib/discordrb/commands/command_bot.rb +13 -21
  29. data/lib/discordrb/commands/container.rb +1 -1
  30. data/lib/discordrb/commands/parser.rb +7 -7
  31. data/lib/discordrb/commands/rate_limiter.rb +1 -1
  32. data/lib/discordrb/container.rb +178 -3
  33. data/lib/discordrb/data/activity.rb +1 -1
  34. data/lib/discordrb/data/application.rb +1 -0
  35. data/lib/discordrb/data/attachment.rb +38 -3
  36. data/lib/discordrb/data/audit_logs.rb +3 -3
  37. data/lib/discordrb/data/avatar_decoration.rb +26 -0
  38. data/lib/discordrb/data/call.rb +22 -0
  39. data/lib/discordrb/data/channel.rb +299 -30
  40. data/lib/discordrb/data/collectibles.rb +45 -0
  41. data/lib/discordrb/data/component.rb +229 -0
  42. data/lib/discordrb/data/embed.rb +10 -3
  43. data/lib/discordrb/data/emoji.rb +20 -1
  44. data/lib/discordrb/data/integration.rb +45 -3
  45. data/lib/discordrb/data/interaction.rb +937 -0
  46. data/lib/discordrb/data/invite.rb +1 -1
  47. data/lib/discordrb/data/member.rb +236 -44
  48. data/lib/discordrb/data/message.rb +278 -51
  49. data/lib/discordrb/data/overwrite.rb +15 -7
  50. data/lib/discordrb/data/primary_server.rb +60 -0
  51. data/lib/discordrb/data/profile.rb +2 -7
  52. data/lib/discordrb/data/reaction.rb +2 -1
  53. data/lib/discordrb/data/recipient.rb +1 -1
  54. data/lib/discordrb/data/role.rb +204 -18
  55. data/lib/discordrb/data/server.rb +194 -118
  56. data/lib/discordrb/data/server_preview.rb +68 -0
  57. data/lib/discordrb/data/snapshot.rb +110 -0
  58. data/lib/discordrb/data/user.rb +132 -12
  59. data/lib/discordrb/data/voice_region.rb +1 -0
  60. data/lib/discordrb/data/webhook.rb +99 -9
  61. data/lib/discordrb/data.rb +9 -0
  62. data/lib/discordrb/errors.rb +47 -3
  63. data/lib/discordrb/events/await.rb +1 -1
  64. data/lib/discordrb/events/channels.rb +38 -1
  65. data/lib/discordrb/events/generic.rb +2 -0
  66. data/lib/discordrb/events/guilds.rb +6 -1
  67. data/lib/discordrb/events/interactions.rb +575 -0
  68. data/lib/discordrb/events/invites.rb +2 -0
  69. data/lib/discordrb/events/members.rb +19 -2
  70. data/lib/discordrb/events/message.rb +42 -8
  71. data/lib/discordrb/events/presence.rb +23 -14
  72. data/lib/discordrb/events/raw.rb +1 -0
  73. data/lib/discordrb/events/reactions.rb +2 -1
  74. data/lib/discordrb/events/roles.rb +2 -0
  75. data/lib/discordrb/events/threads.rb +100 -0
  76. data/lib/discordrb/events/typing.rb +1 -0
  77. data/lib/discordrb/events/voice_server_update.rb +1 -0
  78. data/lib/discordrb/events/voice_state_update.rb +1 -0
  79. data/lib/discordrb/events/webhooks.rb +1 -0
  80. data/lib/discordrb/gateway.rb +57 -28
  81. data/lib/discordrb/paginator.rb +3 -3
  82. data/lib/discordrb/permissions.rb +71 -35
  83. data/lib/discordrb/version.rb +1 -1
  84. data/lib/discordrb/voice/encoder.rb +2 -2
  85. data/lib/discordrb/voice/network.rb +18 -7
  86. data/lib/discordrb/voice/sodium.rb +3 -1
  87. data/lib/discordrb/voice/voice_bot.rb +3 -3
  88. data/lib/discordrb/webhooks.rb +2 -0
  89. data/lib/discordrb/websocket.rb +0 -10
  90. data/lib/discordrb.rb +54 -5
  91. metadata +87 -25
  92. data/.circleci/config.yml +0 -126
  93. data/.codeclimate.yml +0 -16
  94. data/.travis.yml +0 -32
  95. data/bin/travis_build_docs.sh +0 -17
@@ -6,37 +6,56 @@ 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
50
+ 41 => :view_monetization_analytics, # 2199023255552
51
+ 42 => :use_soundboard, # 4398046511104
52
+ 43 => :create_server_expressions, # 8796093022208
53
+ 44 => :create_scheduled_events, # 17592186044416
54
+ 45 => :use_external_sounds, # 35184372088832
55
+ 46 => :send_voice_messages, # 70368744177664
56
+ 49 => :send_polls, # 562949953421312
57
+ 50 => :use_external_apps, # 1125899906842624
58
+ 51 => :pin_messages # 2251799813685248
40
59
  }.freeze
41
60
 
42
61
  FLAGS.each do |position, flag|
@@ -70,7 +89,7 @@ module Discordrb
70
89
  # Initialize the instance variables based on the bitset.
71
90
  def init_vars
72
91
  FLAGS.each do |position, flag|
73
- flag_set = ((@bits >> position) & 0x1) == 1
92
+ flag_set = (@bits >> position).allbits?(0x1)
74
93
  instance_variable_set "@#{flag}", flag_set
75
94
  end
76
95
  end
@@ -101,7 +120,7 @@ module Discordrb
101
120
  # permission.can_speak = true
102
121
  # @example Create a permissions object that could allow/deny read messages, connect, and speak by an array of symbols
103
122
  # 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
123
+ # @param bits [String, Integer, Array<Symbol>] The permission bits that should be set from the beginning, or an array of permission flag symbols
105
124
  # @param writer [RoleWriter] The writer that should be used to update data when a permission is set.
106
125
  def initialize(bits = 0, writer = nil)
107
126
  @writer = writer
@@ -109,15 +128,26 @@ module Discordrb
109
128
  @bits = if bits.is_a? Array
110
129
  self.class.bits(bits)
111
130
  else
112
- bits
131
+ bits.to_i
113
132
  end
114
133
 
115
134
  init_vars
116
135
  end
117
136
 
137
+ # Return an array of permission flag symbols for this class's permissions
138
+ # @example Get the permissions for the bits "9"
139
+ # permissions = Permissions.new(9)
140
+ # permissions.defined_permissions #=> [:create_instant_invite, :administrator]
141
+ # @return [Array<Symbol>] the permissions
142
+ def defined_permissions
143
+ FLAGS.filter_map { |value, name| @bits.anybits?((1 << value)) ? name : nil }
144
+ end
145
+
118
146
  # Comparison based on permission bits
119
147
  def ==(other)
148
+ # rubocop:disable Lint/Void
120
149
  false unless other.is_a? Discordrb::Permissions
150
+ # rubocop:enable Lint/Void
121
151
  bits == other.bits
122
152
  end
123
153
  end
@@ -154,6 +184,12 @@ module Discordrb
154
184
  # has_manage_channels = member.defined_permission?(:manage_channels)
155
185
  # @return [true, false] whether or not this user has the permission defined.
156
186
  def defined_permission?(action, channel = nil)
187
+ # For slash commands we may not have access to the server or role
188
+ # permissions. In this case we use the permissions given to us by the
189
+ # interaction. If attempting to check against a specific channel the check
190
+ # is skipped.
191
+ return @permissions.__send__(action) if @permissions && channel.nil?
192
+
157
193
  # Get the permission the user's roles have
158
194
  role_permission = defined_role_permission?(action, channel)
159
195
 
@@ -179,7 +215,7 @@ module Discordrb
179
215
  private
180
216
 
181
217
  def defined_role_permission?(action, channel)
182
- roles_to_check = [@server.everyone_role] + @roles
218
+ roles_to_check = [@server.everyone_role] + roles
183
219
 
184
220
  # For each role, check if
185
221
  # (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.3'
6
+ VERSION = '3.6.0'
7
7
  end
@@ -102,8 +102,8 @@ module Discordrb::Voice
102
102
  '-ar', '48000',
103
103
  '-ac', '2',
104
104
  'pipe:1',
105
- filter_volume_argument,
106
- ].concat(options.split).reject {|segment| segment.nil? || segment == '' }
105
+ filter_volume_argument
106
+ ].concat(options.split).reject { |segment| segment.nil? || segment == '' }
107
107
  end
108
108
 
109
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
@@ -2,16 +2,6 @@
2
2
 
3
3
  require 'websocket-client-simple'
4
4
 
5
- # The WSCS module which we're hooking
6
- # @see Websocket::Client::Simple::Client
7
- module WebSocket::Client::Simple
8
- # Patch to the WSCS class to allow reading the internal thread
9
- class Client
10
- # @return [Thread] the internal thread this client is using for the event loop.
11
- attr_reader :thread
12
- end
13
- end
14
-
15
5
  module Discordrb
16
6
  # Utility wrapper class that abstracts an instance of WSCS. Useful should we decide that WSCS isn't good either -
17
7
  # in that case we can just switch to something else
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,17 +35,39 @@ 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
- def self.id_compare(one_id, other)
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)
44
51
  end
45
52
 
53
+ # @deprecated Please use {Discordrb.id_compare?}
54
+ singleton_class.alias_method :id_compare, :id_compare?
55
+
46
56
  # The maximum length a Discord message can have
47
57
  CHARACTER_LIMIT = 2000
48
58
 
59
+ # For creating timestamps with {timestamp}
60
+ # @see https://discord.com/developers/docs/reference#message-formatting-timestamp-styles
61
+ TIMESTAMP_STYLES = {
62
+ short_time: 't', # 16:20
63
+ long_time: 'T', # 16:20:30
64
+ short_date: 'd', # 20/04/2021
65
+ long_date: 'D', # 20 April 2021
66
+ short_datetime: 'f', # 20 April 2021 16:20
67
+ long_datetime: 'F', # Tuesday, 20 April 2021 16:20
68
+ relative: 'R' # 2 months ago
69
+ }.freeze
70
+
49
71
  # Splits a message into chunks of 2000 characters. Attempts to split by lines if possible.
50
72
  # @param msg [String] The message to split.
51
73
  # @return [Array<String>] the message split into chunks
@@ -76,7 +98,7 @@ module Discordrb
76
98
  ideal_ary = ideal.length > CHARACTER_LIMIT ? ideal.split(/(.{1,#{CHARACTER_LIMIT}}\b|.{1,#{CHARACTER_LIMIT}})/o).reject(&:empty?) : [ideal]
77
99
 
78
100
  # Slice off the ideal part and strip newlines
79
- rest = msg[ideal.length..-1].strip
101
+ rest = msg[ideal.length..].strip
80
102
 
81
103
  # If none remains, return an empty array -> we're done
82
104
  return [] unless rest
@@ -84,6 +106,33 @@ module Discordrb
84
106
  # Otherwise, call the method recursively to split the rest of the string and add it onto the ideal array
85
107
  ideal_ary + split_message(rest)
86
108
  end
109
+
110
+ # @param time [Time, Integer] The time to create the timestamp from, or a unix timestamp integer.
111
+ # @param style [Symbol, String] One of the keys from {TIMESTAMP_STYLES} or a string with the style.
112
+ # @return [String]
113
+ # @example
114
+ # Discordrb.timestamp(Time.now, :short_time)
115
+ # # => "<t:1632146954:t>"
116
+ def self.timestamp(time, style = nil)
117
+ if style.nil?
118
+ "<t:#{time.to_i}>"
119
+ else
120
+ "<t:#{time.to_i}:#{TIMESTAMP_STYLES[style] || style}>"
121
+ end
122
+ end
123
+
124
+ # A utility method to base64 encode a file like object using its mime type.
125
+ # @param file [File, #read] A file like object that responds to #read.
126
+ # @return [String] The file object encoded as base64 image data.
127
+ def self.encode64(file)
128
+ path_method = %i[original_filename path local_path].find { |method| file.respond_to?(method) }
129
+
130
+ raise ArgumentError, 'File object must respond to original_filename, path, or local path.' unless path_method
131
+ raise ArgumentError, 'File object must respond to read.' unless file.respond_to?(:read)
132
+
133
+ mime_type = MIME::Types.type_for(file.__send__(path_method)).first&.to_s || 'image/jpeg'
134
+ "data:#{mime_type};base64,#{Base64.encode64(file.read).strip}"
135
+ end
87
136
  end
88
137
 
89
138
  # In discordrb, Integer and {String} are monkey-patched to allow for easy resolution of IDs