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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +44 -18
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +419 -222
- data/LICENSE.txt +1 -1
- data/README.md +37 -25
- data/discordrb-webhooks.gemspec +4 -1
- data/discordrb.gemspec +9 -6
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +182 -11
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +2 -2
- data/lib/discordrb/api/server.rb +42 -19
- data/lib/discordrb/api/user.rb +9 -3
- data/lib/discordrb/api/webhook.rb +57 -0
- data/lib/discordrb/api.rb +19 -5
- data/lib/discordrb/bot.rb +328 -33
- data/lib/discordrb/cache.rb +27 -22
- data/lib/discordrb/commands/command_bot.rb +14 -7
- data/lib/discordrb/commands/container.rb +1 -1
- data/lib/discordrb/commands/parser.rb +2 -2
- data/lib/discordrb/commands/rate_limiter.rb +1 -1
- data/lib/discordrb/container.rb +132 -3
- data/lib/discordrb/data/activity.rb +8 -1
- data/lib/discordrb/data/attachment.rb +15 -0
- data/lib/discordrb/data/audit_logs.rb +3 -3
- data/lib/discordrb/data/channel.rb +167 -23
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/integration.rb +42 -3
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +2 -2
- data/lib/discordrb/data/member.rb +108 -33
- data/lib/discordrb/data/message.rb +100 -20
- data/lib/discordrb/data/overwrite.rb +13 -7
- data/lib/discordrb/data/role.rb +58 -1
- data/lib/discordrb/data/server.rb +82 -80
- data/lib/discordrb/data/user.rb +69 -9
- data/lib/discordrb/data/webhook.rb +97 -4
- data/lib/discordrb/data.rb +3 -0
- data/lib/discordrb/errors.rb +44 -3
- data/lib/discordrb/events/channels.rb +1 -1
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/message.rb +9 -6
- data/lib/discordrb/events/presence.rb +21 -14
- data/lib/discordrb/events/reactions.rb +0 -1
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/gateway.rb +30 -17
- data/lib/discordrb/permissions.rb +59 -34
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +13 -4
- data/lib/discordrb/voice/network.rb +18 -7
- data/lib/discordrb/voice/sodium.rb +3 -1
- data/lib/discordrb/voice/voice_bot.rb +3 -3
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +37 -4
- metadata +53 -19
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -32
- 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
|
data/lib/discordrb/gateway.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
}, compress,
|
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
|
-
# - "
|
296
|
-
# - "
|
297
|
-
# - "
|
298
|
-
# - "
|
299
|
-
# - "
|
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
|
-
|
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,
|
10
|
-
1 => :kick_members,
|
11
|
-
2 => :ban_members,
|
12
|
-
3 => :administrator,
|
13
|
-
4 => :manage_channels,
|
14
|
-
5 => :manage_server,
|
15
|
-
6 => :add_reactions,
|
16
|
-
7 => :view_audit_log,
|
17
|
-
8 => :priority_speaker,
|
18
|
-
9 => :stream,
|
19
|
-
10 => :read_messages,
|
20
|
-
11 => :send_messages,
|
21
|
-
12 => :send_tts_messages,
|
22
|
-
13 => :manage_messages,
|
23
|
-
14 => :embed_links,
|
24
|
-
15 => :attach_files,
|
25
|
-
16 => :read_message_history,
|
26
|
-
17 => :mention_everyone,
|
27
|
-
18 => :use_external_emoji,
|
28
|
-
19 => :view_server_insights,
|
29
|
-
20 => :connect,
|
30
|
-
21 => :speak,
|
31
|
-
22 => :mute_members,
|
32
|
-
23 => :deafen_members,
|
33
|
-
24 => :move_members,
|
34
|
-
25 => :use_voice_activity,
|
35
|
-
26 => :change_nickname,
|
36
|
-
27 => :manage_nicknames,
|
37
|
-
28 => :manage_roles,
|
38
|
-
29 => :manage_webhooks,
|
39
|
-
30 => :manage_emojis
|
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] +
|
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
|
data/lib/discordrb/version.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
-
|
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
|
45
|
+
attr_reader :mode
|
46
46
|
|
47
47
|
# @!visibility private
|
48
|
-
attr_writer :mode
|
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(
|
73
|
-
ip = message[
|
74
|
-
port = message[-2
|
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
|
-
|
101
|
+
# Create empty packet
|
102
|
+
discovery_packet = ''
|
102
103
|
|
103
|
-
# Add
|
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
|
|
@@ -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
|
25
|
+
attr_reader :channel
|
26
26
|
|
27
27
|
# @!visibility private
|
28
|
-
attr_writer :channel
|
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://
|
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)
|
data/lib/discordrb/webhooks.rb
CHANGED
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
|
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://
|
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
|
-
#
|
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
|
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
|