discordrb 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|