discordrb 3.1.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of discordrb might be problematic. Click here for more details.

Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +126 -0
  3. data/.codeclimate.yml +16 -0
  4. data/.github/CONTRIBUTING.md +13 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  7. data/.github/pull_request_template.md +37 -0
  8. data/.gitignore +5 -0
  9. data/.rubocop.yml +39 -33
  10. data/.travis.yml +27 -2
  11. data/.yardopts +1 -1
  12. data/CHANGELOG.md +808 -208
  13. data/Gemfile +4 -1
  14. data/LICENSE.txt +1 -1
  15. data/README.md +108 -53
  16. data/Rakefile +14 -1
  17. data/bin/console +1 -0
  18. data/bin/travis_build_docs.sh +17 -0
  19. data/discordrb-webhooks.gemspec +26 -0
  20. data/discordrb.gemspec +24 -15
  21. data/lib/discordrb.rb +75 -2
  22. data/lib/discordrb/allowed_mentions.rb +36 -0
  23. data/lib/discordrb/api.rb +126 -27
  24. data/lib/discordrb/api/channel.rb +165 -43
  25. data/lib/discordrb/api/invite.rb +10 -7
  26. data/lib/discordrb/api/server.rb +240 -61
  27. data/lib/discordrb/api/user.rb +26 -24
  28. data/lib/discordrb/api/webhook.rb +83 -0
  29. data/lib/discordrb/await.rb +1 -2
  30. data/lib/discordrb/bot.rb +417 -149
  31. data/lib/discordrb/cache.rb +42 -10
  32. data/lib/discordrb/colour_rgb.rb +43 -0
  33. data/lib/discordrb/commands/command_bot.rb +186 -31
  34. data/lib/discordrb/commands/container.rb +30 -16
  35. data/lib/discordrb/commands/parser.rb +102 -47
  36. data/lib/discordrb/commands/rate_limiter.rb +18 -17
  37. data/lib/discordrb/container.rb +245 -41
  38. data/lib/discordrb/data.rb +27 -2511
  39. data/lib/discordrb/data/activity.rb +264 -0
  40. data/lib/discordrb/data/application.rb +50 -0
  41. data/lib/discordrb/data/attachment.rb +56 -0
  42. data/lib/discordrb/data/audit_logs.rb +345 -0
  43. data/lib/discordrb/data/channel.rb +849 -0
  44. data/lib/discordrb/data/embed.rb +251 -0
  45. data/lib/discordrb/data/emoji.rb +82 -0
  46. data/lib/discordrb/data/integration.rb +83 -0
  47. data/lib/discordrb/data/invite.rb +137 -0
  48. data/lib/discordrb/data/member.rb +297 -0
  49. data/lib/discordrb/data/message.rb +334 -0
  50. data/lib/discordrb/data/overwrite.rb +102 -0
  51. data/lib/discordrb/data/profile.rb +91 -0
  52. data/lib/discordrb/data/reaction.rb +33 -0
  53. data/lib/discordrb/data/recipient.rb +34 -0
  54. data/lib/discordrb/data/role.rb +191 -0
  55. data/lib/discordrb/data/server.rb +1002 -0
  56. data/lib/discordrb/data/user.rb +204 -0
  57. data/lib/discordrb/data/voice_region.rb +45 -0
  58. data/lib/discordrb/data/voice_state.rb +41 -0
  59. data/lib/discordrb/data/webhook.rb +145 -0
  60. data/lib/discordrb/errors.rb +36 -2
  61. data/lib/discordrb/events/bans.rb +7 -5
  62. data/lib/discordrb/events/channels.rb +2 -0
  63. data/lib/discordrb/events/generic.rb +19 -3
  64. data/lib/discordrb/events/guilds.rb +129 -6
  65. data/lib/discordrb/events/invites.rb +125 -0
  66. data/lib/discordrb/events/members.rb +6 -2
  67. data/lib/discordrb/events/message.rb +86 -36
  68. data/lib/discordrb/events/presence.rb +23 -16
  69. data/lib/discordrb/events/raw.rb +47 -0
  70. data/lib/discordrb/events/reactions.rb +159 -0
  71. data/lib/discordrb/events/roles.rb +7 -6
  72. data/lib/discordrb/events/typing.rb +9 -5
  73. data/lib/discordrb/events/voice_server_update.rb +47 -0
  74. data/lib/discordrb/events/voice_state_update.rb +29 -9
  75. data/lib/discordrb/events/webhooks.rb +64 -0
  76. data/lib/discordrb/gateway.rb +219 -88
  77. data/lib/discordrb/id_object.rb +39 -0
  78. data/lib/discordrb/light.rb +1 -1
  79. data/lib/discordrb/light/integrations.rb +1 -1
  80. data/lib/discordrb/light/light_bot.rb +1 -1
  81. data/lib/discordrb/logger.rb +12 -11
  82. data/lib/discordrb/paginator.rb +57 -0
  83. data/lib/discordrb/permissions.rb +148 -14
  84. data/lib/discordrb/version.rb +1 -1
  85. data/lib/discordrb/voice/encoder.rb +14 -15
  86. data/lib/discordrb/voice/network.rb +86 -45
  87. data/lib/discordrb/voice/sodium.rb +96 -0
  88. data/lib/discordrb/voice/voice_bot.rb +52 -40
  89. data/lib/discordrb/webhooks.rb +12 -0
  90. data/lib/discordrb/websocket.rb +2 -2
  91. metadata +137 -34
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Mixin for objects that have IDs
5
+ module IDObject
6
+ # @return [Integer] the ID which uniquely identifies this object across Discord.
7
+ attr_reader :id
8
+ alias_method :resolve_id, :id
9
+ alias_method :hash, :id
10
+
11
+ # ID based comparison
12
+ def ==(other)
13
+ Discordrb.id_compare(@id, other)
14
+ end
15
+
16
+ alias_method :eql?, :==
17
+
18
+ # Estimates the time this object was generated on based on the beginning of the ID. This is fairly accurate but
19
+ # shouldn't be relied on as Discord might change its algorithm at any time
20
+ # @return [Time] when this object was created at
21
+ def creation_time
22
+ # Milliseconds
23
+ ms = (@id >> 22) + DISCORD_EPOCH
24
+ Time.at(ms / 1000.0)
25
+ end
26
+
27
+ # Creates an artificial snowflake at the given point in time. Useful for comparing against.
28
+ # @param time [Time] The time the snowflake should represent.
29
+ # @return [Integer] a snowflake with the timestamp data as the given time
30
+ def self.synthesise(time)
31
+ ms = (time.to_f * 1000).to_i
32
+ (ms - DISCORD_EPOCH) << 22
33
+ end
34
+
35
+ class << self
36
+ alias_method :synthesize, :synthesise
37
+ end
38
+ end
39
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'discordrb/light/light_bot'
4
4
 
5
- # This module contains classes to allow connections to bots without a connection to the gateway socket, i. e. bots
5
+ # This module contains classes to allow connections to bots without a connection to the gateway socket, i.e. bots
6
6
  # that only use the REST part of the API.
7
7
  module Discordrb::Light
8
8
  end
@@ -49,7 +49,7 @@ module Discordrb::Light
49
49
  # Twitch account connection of the server owner).
50
50
  attr_reader :server_connection
51
51
 
52
- # @return [Connection] the connection integrated with the server (i. e. your connection)
52
+ # @return [Connection] the connection integrated with the server (i.e. your connection)
53
53
  attr_reader :integrated_connection
54
54
 
55
55
  # @!visibility private
@@ -6,7 +6,7 @@ require 'discordrb/api/user'
6
6
  require 'discordrb/light/data'
7
7
  require 'discordrb/light/integrations'
8
8
 
9
- # This module contains classes to allow connections to bots without a connection to the gateway socket, i. e. bots
9
+ # This module contains classes to allow connections to bots without a connection to the gateway socket, i.e. bots
10
10
  # that only use the REST part of the API.
11
11
  module Discordrb::Light
12
12
  # A bot that only uses the REST part of the API. Hierarchically unrelated to the regular {Discordrb::Bot}. Useful to
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Discordrb
4
4
  # The format log timestamps should be in, in strftime format
5
- LOG_TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S.%L'.freeze
5
+ LOG_TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
6
6
 
7
7
  # Logs debug messages
8
8
  class Logger
@@ -18,7 +18,7 @@ module Discordrb
18
18
  # Creates a new logger.
19
19
  # @param fancy [true, false] Whether this logger uses fancy mode (ANSI escape codes to make the output colourful)
20
20
  # @param streams [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
21
- def initialize(fancy = false, streams = [STDOUT])
21
+ def initialize(fancy = false, streams = [$stdout])
22
22
  @fancy = fancy
23
23
  self.mode = :normal
24
24
 
@@ -33,14 +33,15 @@ module Discordrb
33
33
  warn: { long: 'WARN', short: '!', format_code: "\u001B[33m" }, # yellow
34
34
  error: { long: 'ERROR', short: '✗', format_code: "\u001B[31m" }, # red
35
35
  out: { long: 'OUT', short: '→', format_code: "\u001B[36m" }, # cyan
36
- in: { long: 'IN', short: '←', format_code: "\u001B[35m" } # purple
36
+ in: { long: 'IN', short: '←', format_code: "\u001B[35m" }, # purple
37
+ ratelimit: { long: 'RATELIMIT', short: 'R', format_code: "\u001B[41m" } # red background
37
38
  }.freeze
38
39
 
39
40
  # The ANSI format code that resets formatting
40
- FORMAT_RESET = "\u001B[0m".freeze
41
+ FORMAT_RESET = "\u001B[0m"
41
42
 
42
43
  # The ANSI format code that makes something bold
43
- FORMAT_BOLD = "\u001B[1m".freeze
44
+ FORMAT_BOLD = "\u001B[1m"
44
45
 
45
46
  MODES.each do |mode, hash|
46
47
  define_method(mode) do |message|
@@ -65,15 +66,15 @@ module Discordrb
65
66
  def mode=(value)
66
67
  case value
67
68
  when :debug
68
- @enabled_modes = [:debug, :good, :info, :warn, :error, :out, :in]
69
+ @enabled_modes = %i[debug good info warn error out in ratelimit]
69
70
  when :verbose
70
- @enabled_modes = [:good, :info, :warn, :error, :out, :in]
71
+ @enabled_modes = %i[good info warn error out in ratelimit]
71
72
  when :normal
72
- @enabled_modes = [:info, :warn, :error]
73
+ @enabled_modes = %i[info warn error ratelimit]
73
74
  when :quiet
74
- @enabled_modes = [:warn, :error]
75
+ @enabled_modes = %i[warn error]
75
76
  when :silent
76
- @enabled_modes = []
77
+ @enabled_modes = %i[]
77
78
  end
78
79
  end
79
80
 
@@ -91,7 +92,7 @@ module Discordrb
91
92
  timestamp = Time.now.strftime(LOG_TIMESTAMP_FORMAT)
92
93
 
93
94
  # Redact token if set
94
- log = if @token
95
+ log = if @token && @token != ''
95
96
  message.to_s.gsub(@token, 'REDACTED_TOKEN')
96
97
  else
97
98
  message.to_s
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Utility class for wrapping paginated endpoints. It is [Enumerable](https://ruby-doc.org/core-2.5.1/Enumerable.html),
5
+ # similar to an `Array`, so most of the same methods can be used to filter the results of the request
6
+ # that it wraps. If you simply want an array of all of the results, `#to_a` can be called.
7
+ class Paginator
8
+ include Enumerable
9
+
10
+ # Creates a new {Paginator}
11
+ # @param limit [Integer] the maximum number of items to request before stopping
12
+ # @param direction [:up, :down] the order in which results are returned in
13
+ # @yield [Array, nil] the last page of results, or nil if this is the first iteration.
14
+ # This should be used to request the next page of results.
15
+ # @yieldreturn [Array] the next page of results
16
+ def initialize(limit, direction, &block)
17
+ @count = 0
18
+ @limit = limit
19
+ @direction = direction
20
+ @block = block
21
+ end
22
+
23
+ # Yields every item produced by the wrapped request, until it returns
24
+ # no more results or the configured `limit` is reached.
25
+ def each
26
+ last_page = nil
27
+ until limit_check
28
+ page = @block.call(last_page)
29
+ return if page.empty?
30
+
31
+ enumerator = case @direction
32
+ when :down
33
+ page.each
34
+ when :up
35
+ page.reverse_each
36
+ end
37
+
38
+ enumerator.each do |item|
39
+ yield item
40
+ @count += 1
41
+ break if limit_check
42
+ end
43
+
44
+ last_page = page
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Whether the paginator limit has been exceeded
51
+ def limit_check
52
+ return false if @limit.nil?
53
+
54
+ @count >= @limit
55
+ end
56
+ end
57
+ end
@@ -4,8 +4,7 @@ module Discordrb
4
4
  # List of permissions Discord uses
5
5
  class Permissions
6
6
  # This hash maps bit positions to logical permissions.
7
- # I'm not sure what the unlabeled bits are reserved for.
8
- Flags = {
7
+ FLAGS = {
9
8
  # Bit => Permission # Value
10
9
  0 => :create_instant_invite, # 1
11
10
  1 => :kick_members, # 2
@@ -13,10 +12,10 @@ module Discordrb
13
12
  3 => :administrator, # 8
14
13
  4 => :manage_channels, # 16
15
14
  5 => :manage_server, # 32
16
- # 6 # 64
17
- # 7 # 128
18
- # 8 # 256
19
- # 9 # 512
15
+ 6 => :add_reactions, # 64
16
+ 7 => :view_audit_log, # 128
17
+ 8 => :priority_speaker, # 256
18
+ 9 => :stream, # 512
20
19
  10 => :read_messages, # 1024
21
20
  11 => :send_messages, # 2048
22
21
  12 => :send_tts_messages, # 4096
@@ -26,7 +25,7 @@ module Discordrb
26
25
  16 => :read_message_history, # 65536
27
26
  17 => :mention_everyone, # 131072
28
27
  18 => :use_external_emoji, # 262144
29
- # 19 # 524288
28
+ 19 => :view_server_insights, # 524288
30
29
  20 => :connect, # 1048576
31
30
  21 => :speak, # 2097152
32
31
  22 => :mute_members, # 4194304
@@ -35,11 +34,14 @@ module Discordrb
35
34
  25 => :use_voice_activity, # 33554432
36
35
  26 => :change_nickname, # 67108864
37
36
  27 => :manage_nicknames, # 134217728
38
- 28 => :manage_roles # 268435456, also Manage Permissions
37
+ 28 => :manage_roles, # 268435456, also Manage Permissions
38
+ 29 => :manage_webhooks, # 536870912
39
+ 30 => :manage_emojis # 1073741824
39
40
  }.freeze
40
41
 
41
- Flags.each do |position, flag|
42
+ FLAGS.each do |position, flag|
42
43
  attr_reader flag
44
+
43
45
  define_method "can_#{flag}=" do |value|
44
46
  new_bits = @bits
45
47
  if value
@@ -47,18 +49,19 @@ module Discordrb
47
49
  else
48
50
  new_bits &= ~(1 << position)
49
51
  end
50
- @writer.write(new_bits) if @writer
52
+ @writer&.write(new_bits)
51
53
  @bits = new_bits
52
54
  init_vars
53
55
  end
54
56
  end
55
57
 
56
58
  alias_method :can_administrate=, :can_administrator=
59
+ alias_method :administrate, :administrator
57
60
 
58
61
  attr_reader :bits
59
62
 
60
63
  # Set the raw bitset of this permission object
61
- # @param bits [Fixnum] A number whose binary representation is the desired bitset.
64
+ # @param bits [Integer] A number whose binary representation is the desired bitset.
62
65
  def bits=(bits)
63
66
  @bits = bits
64
67
  init_vars
@@ -66,20 +69,151 @@ module Discordrb
66
69
 
67
70
  # Initialize the instance variables based on the bitset.
68
71
  def init_vars
69
- Flags.each do |position, flag|
72
+ FLAGS.each do |position, flag|
70
73
  flag_set = ((@bits >> position) & 0x1) == 1
71
74
  instance_variable_set "@#{flag}", flag_set
72
75
  end
73
76
  end
74
77
 
78
+ # Return the corresponding bits for an array of permission flag symbols.
79
+ # This is a class method that can be used to calculate bits instead
80
+ # of instancing a new Permissions object.
81
+ # @example Get the bits for permissions that could allow/deny read messages, connect, and speak
82
+ # Permissions.bits [:read_messages, :connect, :speak] #=> 3146752
83
+ # @param list [Array<Symbol>]
84
+ # @return [Integer] the computed permissions integer
85
+ def self.bits(list)
86
+ value = 0
87
+
88
+ FLAGS.each do |position, flag|
89
+ value += 2**position if list.include? flag
90
+ end
91
+
92
+ value
93
+ end
94
+
75
95
  # Create a new Permissions object either as a blank slate to add permissions to (for example for
76
96
  # {Channel#define_overwrite}) or from existing bit data to read out.
77
- # @param bits [Integer] The permission bits that should be set from the beginning.
97
+ # @example Create a permissions object that could allow/deny read messages, connect, and speak by setting flags
98
+ # permission = Permissions.new
99
+ # permission.can_read_messages = true
100
+ # permission.can_connect = true
101
+ # permission.can_speak = true
102
+ # @example Create a permissions object that could allow/deny read messages, connect, and speak by an array of symbols
103
+ # 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
78
105
  # @param writer [RoleWriter] The writer that should be used to update data when a permission is set.
79
106
  def initialize(bits = 0, writer = nil)
80
107
  @writer = writer
81
- @bits = bits
108
+
109
+ @bits = if bits.is_a? Array
110
+ self.class.bits(bits)
111
+ else
112
+ bits
113
+ end
114
+
82
115
  init_vars
83
116
  end
117
+
118
+ # Comparison based on permission bits
119
+ def ==(other)
120
+ false unless other.is_a? Discordrb::Permissions
121
+ bits == other.bits
122
+ end
123
+ end
124
+
125
+ # Mixin to calculate resulting permissions from overrides etc.
126
+ module PermissionCalculator
127
+ # Checks whether this user can do the particular action, regardless of whether it has the permission defined,
128
+ # through for example being the server owner or having the Manage Roles permission
129
+ # @param action [Symbol] The permission that should be checked. See also {Permissions::FLAGS} for a list.
130
+ # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
131
+ # @example Check if the bot can send messages to a specific channel in a server.
132
+ # bot_profile = bot.profile.on(event.server)
133
+ # can_send_messages = bot_profile.permission?(:send_messages, channel)
134
+ # @return [true, false] whether or not this user has the permission.
135
+ def permission?(action, channel = nil)
136
+ # If the member is the server owner, it irrevocably has all permissions.
137
+ return true if owner?
138
+
139
+ # First, check whether the user has Manage Roles defined.
140
+ # (Coincidentally, Manage Permissions is the same permission as Manage Roles, and a
141
+ # Manage Permissions deny overwrite will override Manage Roles, so we can just check for
142
+ # Manage Roles once and call it a day.)
143
+ return true if defined_permission?(:administrator, channel)
144
+
145
+ # Otherwise, defer to defined_permission
146
+ defined_permission?(action, channel)
147
+ end
148
+
149
+ # Checks whether this user has a particular permission defined (i.e. not implicit, through for example
150
+ # Manage Roles)
151
+ # @param action [Symbol] The permission that should be checked. See also {Permissions::FLAGS} for a list.
152
+ # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
153
+ # @example Check if a member has the Manage Channels permission defined in the server.
154
+ # has_manage_channels = member.defined_permission?(:manage_channels)
155
+ # @return [true, false] whether or not this user has the permission defined.
156
+ def defined_permission?(action, channel = nil)
157
+ # Get the permission the user's roles have
158
+ role_permission = defined_role_permission?(action, channel)
159
+
160
+ # Once we have checked the role permission, we have to check the channel overrides for the
161
+ # specific user
162
+ user_specific_override = permission_overwrite(action, channel, id) # Use the ID reader as members have no ID instance variable
163
+
164
+ # Merge the two permissions - if an override is defined, it has to be allow, otherwise we only care about the role
165
+ return role_permission unless user_specific_override
166
+
167
+ user_specific_override == :allow
168
+ end
169
+
170
+ # Define methods for querying permissions
171
+ Discordrb::Permissions::FLAGS.each_value do |flag|
172
+ define_method "can_#{flag}?" do |channel = nil|
173
+ permission? flag, channel
174
+ end
175
+ end
176
+
177
+ alias_method :can_administrate?, :can_administrator?
178
+
179
+ private
180
+
181
+ def defined_role_permission?(action, channel)
182
+ roles_to_check = [@server.everyone_role] + @roles
183
+
184
+ # For each role, check if
185
+ # (1) the channel explicitly allows or permits an action for the role and
186
+ # (2) if the user is allowed to do the action if the channel doesn't specify
187
+ roles_to_check.sort_by(&:position).reduce(false) do |can_act, role|
188
+ # Get the override defined for the role on the channel
189
+ channel_allow = permission_overwrite(action, channel, role.id)
190
+ if channel_allow
191
+ # If the channel has an override, check whether it is an allow - if yes,
192
+ # the user can act, if not, it can't
193
+ break true if channel_allow == :allow
194
+
195
+ false
196
+ else
197
+ # Otherwise defer to the role
198
+ role.permissions.instance_variable_get("@#{action}") || can_act
199
+ end
200
+ end
201
+ end
202
+
203
+ def permission_overwrite(action, channel, id)
204
+ # If no overwrites are defined, or no channel is set, no overwrite will be present
205
+ return nil unless channel && channel.permission_overwrites[id]
206
+
207
+ # Otherwise, check the allow and deny objects
208
+ allow = channel.permission_overwrites[id].allow
209
+ deny = channel.permission_overwrites[id].deny
210
+ if allow.instance_variable_get("@#{action}")
211
+ :allow
212
+ elsif deny.instance_variable_get("@#{action}")
213
+ :deny
214
+ end
215
+
216
+ # If there's no variable defined, nil will implicitly be returned
217
+ end
84
218
  end
85
219
  end
@@ -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.1.1'.freeze
6
+ VERSION = '3.4.0'
7
7
  end
@@ -24,16 +24,14 @@ module Discordrb::Voice
24
24
 
25
25
  # Create a new encoder
26
26
  def initialize
27
- @sample_rate = 48_000
28
- @frame_size = 960
29
- @channels = 2
27
+ sample_rate = 48_000
28
+ frame_size = 960
29
+ channels = 2
30
30
  @filter_volume = 1
31
31
 
32
- if OPUS_AVAILABLE
33
- @opus = Opus::Encoder.new(@sample_rate, @frame_size, @channels)
34
- else
35
- raise LoadError, 'Opus unavailable - voice not supported! Please install opus for voice support to work.'
36
- end
32
+ raise LoadError, 'Opus unavailable - voice not supported! Please install opus for voice support to work.' unless OPUS_AVAILABLE
33
+
34
+ @opus = Opus::Encoder.new(sample_rate, frame_size, channels)
37
35
  end
38
36
 
39
37
  # Set the opus encoding bitrate
@@ -76,21 +74,21 @@ module Discordrb::Voice
76
74
  # an audio track. For a list of supported formats, see https://ffmpeg.org/general.html#Audio-Codecs. It even accepts
77
75
  # URLs, though encoding them is pretty slow - I recommend to make a stream of it and then use {#encode_io} instead.
78
76
  # @param file [String] The path or URL to encode.
77
+ # @param options [String] ffmpeg options to pass after the -i flag
79
78
  # @return [IO] the audio, encoded as s16le PCM
80
- def encode_file(file)
81
- command = "#{ffmpeg_command} -loglevel 0 -i \"#{file}\" -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
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"
82
81
  IO.popen(command)
83
82
  end
84
83
 
85
84
  # Encodes an arbitrary IO audio stream using ffmpeg. Accepts pretty much any media format, even videos with audio
86
85
  # tracks. For a list of supported audio formats, see https://ffmpeg.org/general.html#Audio-Codecs.
87
86
  # @param io [IO] The stream to encode.
87
+ # @param options [String] ffmpeg options to pass after the -i flag
88
88
  # @return [IO] the audio, encoded as s16le PCM
89
- def encode_io(io)
90
- ret_io, writer = IO.pipe
91
- command = "#{ffmpeg_command} -loglevel 0 -i - -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
92
- spawn(command, in: io, out: writer)
93
- ret_io
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"
91
+ IO.popen(command, in: io)
94
92
  end
95
93
 
96
94
  private
@@ -101,6 +99,7 @@ module Discordrb::Voice
101
99
 
102
100
  def filter_volume_argument
103
101
  return '' if @filter_volume == 1
102
+
104
103
  @use_avconv ? "-vol #{(@filter_volume * 256).ceil}" : "-af volume=#{@filter_volume}"
105
104
  end
106
105
  end