discordrb 3.0.2 → 3.1.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c072a97e659190bb7de8ba2f2ebe7483b7064b20
4
- data.tar.gz: dafe2d7da6257050652b82c864478d3a3225c641
3
+ metadata.gz: 63b47e7d0728741bab426ae48763d83324c2096d
4
+ data.tar.gz: bc0bb96095c0f670d172c054673f78641dadc061
5
5
  SHA512:
6
- metadata.gz: 9891d792cbe3c1b34b775a90a8829e136fc0f80a7166a98e4456d57a8833bd3a7d96c8cd512ffaa2cb25c9881c724a25a9eb209395117e06250c5c979abbb506
7
- data.tar.gz: 5f67ff2dcaa46e19a4c5bf94c67838a0f21bbc47831553a585278018f0b4b3af1372badf2c3b68d5fad528f2ee9536ea57b009168e162f58c854bd508f124a45
6
+ metadata.gz: 64aa1b967a77ab2b0dc0d2ce1679436d2da83f2b332c803b2564c08eabef65ab65fb6499ea861cf9514f19cf77c76120f2b7b2f3eb5289b484e7031dc2c0f83e
7
+ data.tar.gz: 928cebe04848e4aa4db8427c1e821f6a03dbade08bca3d7d7da0e13498f896bef996024e978826a2242c0cbf3c63115072e674ed808e5c1f81abf3dff44568ec
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.1.0
4
+
5
+ - Emoji handling support ([#226](https://github.com/meew0/discordrb/pull/226), thanks @greenbigfrog)
6
+ - A `channels` attribute has been added to `CommandBot` as well as `Command` to restrict the channels in which either of the two works ([#249](https://github.com/meew0/discordrb/pull/249), thanks @Xzanth)
7
+ - The bulk deletion endpoint is now exposed directly using the `Channel#delete_messages` method ([#235](https://github.com/meew0/discordrb/pull/235), thanks @z64)
8
+ - The internal settings fields for user statuses that cause statuses to persist across restarts can now be modified ([#233](https://github.com/meew0/discordrb/pull/233), thanks @Daniel-Worrall)
9
+ - A few examples have been added to the docs ([#250](https://github.com/meew0/discordrb/pull/250), thanks @SunDwarf)
10
+ - The specs have been improved; they're still not exhaustive by far but there are at least slightly more now.
11
+
12
+ ### Bugfixes
13
+
14
+ - Fixed an important bug that caused the logger not to work in some cases. ([#243](https://github.com/meew0/discordrb/pull/243), thanks @Daniel-Worrall)
15
+ - Fixed logger token redaction.
16
+ - `unavailable_servers` should no longer crash the bot due to being nil in some cases ([#244](https://github.com/meew0/discordrb/pull/244), thanks @Daniel-Worrall)
17
+ - `Profile#on` for member resolution is now no longer overwritten by an alias for `#online` ([#247](https://github.com/meew0/discordrb/pull/247), thanks @Daniel-Worrall)
18
+ - A `CommandBot` without any commands should no longer crash when receiving a message that triggers it ([#242](https://github.com/meew0/discordrb/issues/242))
19
+ - Changing nicknames works again, it has apparently been broken in 3.0.0.
20
+
3
21
  ## 3.0.2
4
22
 
5
23
  - A small change to how CommandBot parameter lists are formatted ([#240](https://github.com/meew0/discordrb/pull/240), thanks @FormalHellhound)
data/README.md CHANGED
@@ -86,7 +86,7 @@ You can make a simple bot like this:
86
86
  ```ruby
87
87
  require 'discordrb'
88
88
 
89
- bot = Discordrb::Bot.new token: '<token here>', application_id: 168123456789123456
89
+ bot = Discordrb::Bot.new token: '<token here>', client_id: 168123456789123456
90
90
 
91
91
  bot.message(with_text: 'Ping!') do |event|
92
92
  event.respond 'Pong!'
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/meew0/discordrb'
15
15
  spec.license = 'MIT'
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples)/}) }
18
18
  spec.bindir = 'exe'
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
@@ -137,6 +137,11 @@ module Discordrb::API
137
137
  "#{api_base}/guilds/#{server_id}/widget.png?style=#{style}"
138
138
  end
139
139
 
140
+ # Make an emoji icon URL from emoji ID
141
+ def emoji_icon_url(emoji_id)
142
+ "https://cdn.discordapp.com/emojis/#{emoji_id}.png"
143
+ end
144
+
140
145
  # Login to the server
141
146
  def login(email, password)
142
147
  request(
@@ -127,6 +127,19 @@ module Discordrb::API::User
127
127
  )
128
128
  end
129
129
 
130
+ # Change user status setting
131
+ def change_status_setting(token, status)
132
+ Discordrb::API.request(
133
+ :users_me_settings,
134
+ nil,
135
+ :patch,
136
+ "#{Discordrb::API.api_base}/users/@me/settings",
137
+ { status: status }.to_json,
138
+ Authorization: token,
139
+ content_type: :json
140
+ )
141
+ end
142
+
130
143
  # Make an avatar URL from the user and avatar IDs
131
144
  def avatar_url(user_id, avatar_id)
132
145
  "#{Discordrb::API.api_base}/users/#{user_id}/avatars/#{avatar_id}.jpg"
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'discordrb/container'
4
-
5
3
  module Discordrb
6
4
  # Awaits are a way to register new, temporary event handlers on the fly. Awaits can be
7
5
  # registered using {Bot#add_await}, {User#await}, {Message#await} and {Channel#await}.
@@ -152,6 +152,39 @@ module Discordrb
152
152
  @servers
153
153
  end
154
154
 
155
+ # @overload emoji(id)
156
+ # Return an emoji by its ID
157
+ # @param id [Integer] The emoji's ID.
158
+ # @return emoji [GlobalEmoji, nil] the emoji object. `nil` if the emoji was not found.
159
+ # @overload emoji
160
+ # The list of emoji the bot can use.
161
+ # @return [Array<GlobalEmoji>] the emoji available.
162
+ def emoji(id = nil)
163
+ gateway_check
164
+ if id
165
+ emoji = @emoji.find { |sth| sth.id == id }
166
+ else
167
+ emoji = {}
168
+ @servers.each do |_, server|
169
+ server.emoji.values.each do |element|
170
+ emoji[element.name] = GlobalEmoji.new(element, self)
171
+ end
172
+ end
173
+ emoji.values
174
+ end
175
+ end
176
+
177
+ alias_method :emojis, :emoji
178
+ alias_method :all_emoji, :emoji
179
+
180
+ # Finds an emoji by its name.
181
+ # @param name [String] The emoji name that should be resolved.
182
+ # @return [GlobalEmoji, nil] the emoji identified by the name, or `nil` if it couldn't be found.
183
+ def find_emoji(name)
184
+ LOGGER.out("Resolving emoji #{name}")
185
+ emoji.find { |element| element.name == name }
186
+ end
187
+
155
188
  # The bot's user profile. This special user object can be used
156
189
  # to edit user data like the current username (see {Profile#username=}).
157
190
  # @return [Profile] The bot's profile that can be used to edit data.
@@ -398,13 +431,23 @@ module Discordrb
398
431
  API.update_oauth_application(@token, name, redirect_uris, description, icon)
399
432
  end
400
433
 
401
- # Gets the user from a mention of the user.
402
- # @param mention [String] The mention, which should look like <@12314873129>.
403
- # @return [User] The user identified by the mention, or `nil` if none exists.
404
- def parse_mention(mention)
434
+ # Gets the user, role or emoji from a mention of the user, role or emoji.
435
+ # @param mention [String] The mention, which should look like `<@12314873129>`, `<@&123456789>` or `<:Name:126328:>`.
436
+ # @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up)
437
+ # @return [User, Role, Emoji] The user, role or emoji identified by the mention, or `nil` if none exists.
438
+ def parse_mention(mention, server = nil)
405
439
  # Mention format: <@id>
406
- return nil unless /<@!?(?<id>\d+)>?/ =~ mention
407
- user(id.to_i)
440
+ if /<@!?(?<id>\d+)>?/ =~ mention
441
+ user(id.to_i)
442
+ elsif /<@&(?<id>\d+)>?/ =~ mention
443
+ return server.role(id) if server
444
+ servers.each do |element|
445
+ role = element.role(id)
446
+ return role unless role.nil?
447
+ end
448
+ elsif /<:(\w+):(?<id>\d+)>?/ =~ mention
449
+ emoji.find { |element| element.id.to_i == id.to_i }
450
+ end
408
451
  end
409
452
 
410
453
  # Updates presence status.
@@ -812,7 +855,7 @@ module Discordrb
812
855
  LOGGER.warn("This means some servers are unavailable due to an outage. Notifying ready now, we'll have to live without these servers")
813
856
 
814
857
  # Unset the unavailable server count so this doesn't get triggered again
815
- @unavailable_servers = nil
858
+ @unavailable_servers = 0
816
859
 
817
860
  notify_ready
818
861
  end
@@ -50,6 +50,8 @@ module Discordrb::Commands
50
50
  # command. Default is false.
51
51
  # @option attributes [true, false] :webhook_commands Whether messages sent by webhooks are allowed to trigger
52
52
  # commands. Default is true.
53
+ # @option attributes [Array<String, Integer, Channel>] :channels The channels this command bot accepts commands on.
54
+ # Superseded if a command has a 'channels' attribute.
53
55
  # @option attributes [String] :previous Character that should designate the result of the previous command in
54
56
  # a command chain (see :advanced_functionality). Default is '~'.
55
57
  # @option attributes [String] :chain_delimiter Character that should designate that a new command begins in the
@@ -77,7 +79,7 @@ module Discordrb::Commands
77
79
  parse_self: attributes[:parse_self],
78
80
  shard_id: attributes[:shard_id],
79
81
  num_shards: attributes[:num_shards],
80
- redact_token: attributes[:redact_token])
82
+ redact_token: attributes.key?(:redact_token) ? attributes[:redact_token] : true)
81
83
 
82
84
  @prefix = attributes[:prefix]
83
85
  @attributes = {
@@ -100,6 +102,8 @@ module Discordrb::Commands
100
102
  # Webhooks allowed to trigger commands
101
103
  webhook_commands: attributes[:webhook_commands].nil? ? true : attributes[:webhook_commands],
102
104
 
105
+ channels: attributes[:channels] || [],
106
+
103
107
  # All of the following need to be one character
104
108
  # String to designate previous result in command chain
105
109
  previous: attributes[:previous] || '~',
@@ -169,19 +173,27 @@ module Discordrb::Commands
169
173
  # @param arguments [Array<String>] The arguments to pass to the command.
170
174
  # @param chained [true, false] Whether or not it should be executed as part of a command chain. If this is false,
171
175
  # commands that have chain_usable set to false will not work.
176
+ # @param check_permissions [true, false] Whether permission parameters such as `required_permission` or
177
+ # `permission_level` should be checked.
172
178
  # @return [String, nil] the command's result, if there is any.
173
- def execute_command(name, event, arguments, chained = false)
179
+ def execute_command(name, event, arguments, chained = false, check_permissions = true)
174
180
  debug("Executing command #{name} with arguments #{arguments}")
181
+ return unless @commands
175
182
  command = @commands[name]
183
+ return unless !check_permissions || channels?(event.channel, @attributes[:channels]) ||
184
+ (command && !command.attributes[:channels].nil?)
176
185
  unless command
177
186
  event.respond @attributes[:command_doesnt_exist_message].gsub('%command%', name.to_s) if @attributes[:command_doesnt_exist_message]
178
187
  return
179
188
  end
180
- if permission?(event.author, command.attributes[:permission_level], event.server) &&
189
+ return unless !check_permissions || channels?(event.channel, command.attributes[:channels])
190
+ if (check_permissions &&
191
+ permission?(event.author, command.attributes[:permission_level], event.server) &&
181
192
  required_permissions?(event.author, command.attributes[:required_permissions], event.channel) &&
182
- required_roles?(event.author, command.attributes[:required_roles])
193
+ required_roles?(event.author, command.attributes[:required_roles])) ||
194
+ !check_permissions
183
195
  event.command = command
184
- result = command.call(event, arguments, chained)
196
+ result = command.call(event, arguments, chained, check_permissions)
185
197
  stringify(result)
186
198
  else
187
199
  event.respond command.attributes[:permission_message].gsub('%name%', name.to_s) if command.attributes[:permission_message]
@@ -301,6 +313,20 @@ module Discordrb::Commands
301
313
  end
302
314
  end
303
315
 
316
+ def channels?(channel, channels)
317
+ return true if channels.nil? || channels.empty?
318
+ channels.any? do |c|
319
+ if c.is_a? String
320
+ # Make sure to remove the "#" from channel names in case it was specified
321
+ c.delete('#') == channel.name
322
+ elsif c.is_a? Fixnum
323
+ c == channel.id
324
+ else
325
+ c == channel
326
+ end
327
+ end
328
+ end
329
+
304
330
  def execute_chain(chain, event)
305
331
  t = Thread.new do
306
332
  @event_threads << t
@@ -23,6 +23,8 @@ module Discordrb::Commands
23
23
  # @option attributes [Array<Symbol>] :required_permissions Discord action permissions (e.g. `:kick_members`) that
24
24
  # should be required to use this command. See {Discordrb::Permissions::Flags} for a list.
25
25
  # @option attributes [Array<Role>, Array<#resolve_id>] :required_roles Roles that user should have to use this command.
26
+ # @option attributes [Array<String, Integer, Channel>] :channels The channels that this command can be used on. An
27
+ # empty array indicates it can be used on any channel. Supersedes the command bot attribute.
26
28
  # @option attributes [true, false] :chain_usable Whether this command is able to be used inside of a command chain
27
29
  # or sub-chain. Typically used for administrative commands that shouldn't be done carelessly.
28
30
  # @option attributes [true, false] :help_available Whether this command is visible in the help command. See the
@@ -25,6 +25,9 @@ module Discordrb::Commands
25
25
  # Roles required to use this command
26
26
  required_roles: attributes[:required_roles] || [],
27
27
 
28
+ # Channels this command can be used on
29
+ channels: attributes[:channels] || nil,
30
+
28
31
  # Whether this command is usable in a command chain
29
32
  chain_usable: attributes[:chain_usable].nil? ? true : attributes[:chain_usable],
30
33
 
@@ -61,8 +64,10 @@ module Discordrb::Commands
61
64
  # @param event [CommandEvent] The event to call the command with.
62
65
  # @param arguments [Array<String>] The attributes for the command.
63
66
  # @param chained [true, false] Whether or not this command is part of a command chain.
67
+ # @param check_permissions [true, false] Whether the user's permission to execute the command (i.e. rate limits)
68
+ # should be checked.
64
69
  # @return [String] the result of the execution.
65
- def call(event, arguments, chained = false)
70
+ def call(event, arguments, chained = false, check_permissions = true)
66
71
  if arguments.length < @attributes[:min_args]
67
72
  event.respond "Too few arguments for command `#{name}`!"
68
73
  event.respond "Usage: `#{@attributes[:usage]}`" if @attributes[:usage]
@@ -80,12 +85,14 @@ module Discordrb::Commands
80
85
  end
81
86
  end
82
87
 
83
- rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
84
- if @attributes[:bucket] && rate_limited
85
- if @attributes[:rate_limit_message]
86
- event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s)
88
+ if check_permissions
89
+ rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
90
+ if @attributes[:bucket] && rate_limited
91
+ if @attributes[:rate_limit_message]
92
+ event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s)
93
+ end
94
+ return
87
95
  end
88
- return
89
96
  end
90
97
 
91
98
  result = @block.call(event, *arguments)
@@ -10,7 +10,6 @@ require 'discordrb/api/channel'
10
10
  require 'discordrb/api/server'
11
11
  require 'discordrb/api/invite'
12
12
  require 'discordrb/api/user'
13
- require 'discordrb/events/message'
14
13
  require 'time'
15
14
  require 'base64'
16
15
 
@@ -262,7 +261,7 @@ module Discordrb
262
261
  end
263
262
 
264
263
  # Utility function to get a application's icon URL.
265
- # @return [String, nil] the URL to the icon image (nil if no iamge is set).
264
+ # @return [String, nil] the URL to the icon image (nil if no image is set).
266
265
  def icon_url
267
266
  return nil if @icon_id.nil?
268
267
  API.app_icon_url(@id, @icon_id)
@@ -296,6 +295,9 @@ module Discordrb
296
295
  # through for example being the server owner or having the Manage Roles permission
297
296
  # @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
298
297
  # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
298
+ # @example Check if the bot can send messages to a specific channel in a server.
299
+ # bot_profile = bot.profile.on(event.server)
300
+ # can_send_messages = bot_profile.permission?(:send_messages, channel)
299
301
  # @return [true, false] whether or not this user has the permission.
300
302
  def permission?(action, channel = nil)
301
303
  # If the member is the server owner, it irrevocably has all permissions.
@@ -315,6 +317,8 @@ module Discordrb
315
317
  # Manage Roles)
316
318
  # @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
317
319
  # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
320
+ # @example Check if a member has the Manage Channels permission defined in the server.
321
+ # has_manage_channels = member.defined_permission?(:manage_channels)
318
322
  # @return [true, false] whether or not this user has the permission defined.
319
323
  def defined_permission?(action, channel = nil)
320
324
  # Get the permission the user's roles have
@@ -489,6 +493,10 @@ module Discordrb
489
493
  # Adds and removes roles from a member.
490
494
  # @param add [Role, Array<Role>] The role(s) to add.
491
495
  # @param remove [Role, Array<Role>] The role(s) to remove.
496
+ # @example Remove the 'Member' role from a user, and add the 'Muted' role to them.
497
+ # to_add = server.roles.find {|role| role.name == 'Muted'}
498
+ # to_remove = server.roles.find {|role| role.name == 'Member'}
499
+ # member.modify_roles(to_add, to_remove)
492
500
  def modify_roles(add, remove)
493
501
  add_role_ids = role_id_array(add)
494
502
  remove_role_ids = role_id_array(remove)
@@ -548,7 +556,7 @@ module Discordrb
548
556
  if @user.current_bot?
549
557
  API::User.change_own_nickname(@bot.token, @server.id, nick)
550
558
  else
551
- API.change_nickname(@bot.token, @server.id, @user.id, nick)
559
+ API::Server.update_member(@bot.token, @server.id, @user.id, nick: nick)
552
560
  end
553
561
  end
554
562
 
@@ -676,6 +684,32 @@ module Discordrb
676
684
  @avatar_id = new_data[:avatar_id] || @avatar_id
677
685
  end
678
686
 
687
+ # Sets the user status setting to Online.
688
+ # @note Only usable on User accounts.
689
+ def online
690
+ update_profile_status_setting('online')
691
+ end
692
+
693
+ # Sets the user status setting to Idle.
694
+ # @note Only usable on User accounts.
695
+ def idle
696
+ update_profile_status_setting('idle')
697
+ end
698
+
699
+ # Sets the user status setting to Do Not Disturb.
700
+ # @note Only usable on User accounts.
701
+ def dnd
702
+ update_profile_status_setting('dnd')
703
+ end
704
+
705
+ alias_method(:busy, :dnd)
706
+
707
+ # Sets the user status setting to Invisible.
708
+ # @note Only usable on User accounts.
709
+ def invisible
710
+ update_profile_status_setting('invisible')
711
+ end
712
+
679
713
  # The inspect method is overwritten to give more useful output
680
714
  def inspect
681
715
  "<Profile user=#{super}>"
@@ -683,6 +717,11 @@ module Discordrb
683
717
 
684
718
  private
685
719
 
720
+ # Internal handler for updating the user's status setting
721
+ def update_profile_status_setting(status)
722
+ API::User.change_status_setting(@bot.token, status)
723
+ end
724
+
686
725
  def update_profile_data(new_data)
687
726
  API::User.update_profile(@bot.token,
688
727
  nil, nil,
@@ -729,6 +768,11 @@ module Discordrb
729
768
  def write(bits)
730
769
  @role.send(:packed=, bits, false)
731
770
  end
771
+
772
+ # The inspect method is overridden, in this case to prevent the token being leaked
773
+ def inspect
774
+ "<RoleWriter role=#{@role} token=...>"
775
+ end
732
776
  end
733
777
 
734
778
  # @!visibility private
@@ -822,7 +866,7 @@ module Discordrb
822
866
  @permissions.bits = packed if update_perms
823
867
  end
824
868
 
825
- # Delets this role. This cannot be undone without recreating the role!
869
+ # Deletes this role. This cannot be undone without recreating the role!
826
870
  def delete
827
871
  API::Server.delete_role(@bot.token, @server.id, @id)
828
872
  @server.delete_role(@id)
@@ -1213,6 +1257,8 @@ module Discordrb
1213
1257
  # start at the current message.
1214
1258
  # @param after_id [Integer] The ID of the oldest message the retrieval should start at, or nil if it should start
1215
1259
  # as soon as possible with the specified amount.
1260
+ # @example Count the number of messages in the last 50 messages that contain the letter 'e'.
1261
+ # message_count = channel.history(50).count {|message| message.content.include? "e"}
1216
1262
  # @return [Array<Message>] the retrieved messages.
1217
1263
  def history(amount, before_id = nil, after_id = nil)
1218
1264
  logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id)
@@ -1256,6 +1302,16 @@ module Discordrb
1256
1302
  API::Channel.bulk_delete_messages(@bot.token, @id, messages)
1257
1303
  end
1258
1304
 
1305
+ # Deletes a collection of messages
1306
+ # @param messages [Array<Message, Integer>] the messages (or message IDs) to delete. Total must be an amount between 2 and 100 (Discord limitation)
1307
+ # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
1308
+ def delete_messages(messages)
1309
+ raise ArgumentError, 'Can only delete between 2 and 100 messages!' unless messages.count.between?(2, 100)
1310
+
1311
+ messages.map!(&:resolve_id)
1312
+ API::Channel.bulk_delete_messages(@bot.token, @id, messages)
1313
+ end
1314
+
1259
1315
  # Updates the cached permission overwrites
1260
1316
  # @note For internal use only
1261
1317
  # @!visibility private
@@ -1628,6 +1684,8 @@ module Discordrb
1628
1684
  @edited = !@edited_timestamp.nil?
1629
1685
  @id = data['id'].to_i
1630
1686
 
1687
+ @emoji = []
1688
+
1631
1689
  @mentions = []
1632
1690
 
1633
1691
  data['mentions'].each do |element|
@@ -1657,6 +1715,7 @@ module Discordrb
1657
1715
  end
1658
1716
 
1659
1717
  # Edits this message to have the specified content instead.
1718
+ # You can only edit your own messages.
1660
1719
  # @param new_content [String] the new content the message should have.
1661
1720
  # @return [Message] the resulting message.
1662
1721
  def edit(new_content)
@@ -1700,12 +1759,141 @@ module Discordrb
1700
1759
  !@webhook_id.nil?
1701
1760
  end
1702
1761
 
1762
+ # @!visibility private
1763
+ # @return [Array<String>] the emoji mentions found in the message
1764
+ def scan_for_emoji
1765
+ emoji = @content.split
1766
+ emoji = emoji.grep(/<:(?<name>\w+):(?<id>\d+)>?/)
1767
+ emoji
1768
+ end
1769
+
1770
+ # @return [Array<Emoji>] the emotes that were used/mentioned in this message (Only returns Emoji the bot has access to, else nil).
1771
+ def emoji
1772
+ return if @content.nil?
1773
+
1774
+ emoji = scan_for_emoji
1775
+ emoji.each do |element|
1776
+ @emoji << @bot.parse_mention(element)
1777
+ end
1778
+ @emoji
1779
+ end
1780
+
1781
+ # Check if any emoji got used in this message
1782
+ # @return [true, false] whether or not any emoji got used
1783
+ def emoji?
1784
+ emoji = scan_for_emoji
1785
+ return true unless emoji.empty?
1786
+ end
1787
+
1703
1788
  # The inspect method is overwritten to give more useful output
1704
1789
  def inspect
1705
1790
  "<Message content=\"#{@content}\" id=#{@id} timestamp=#{@timestamp} author=#{@author} channel=#{@channel}>"
1706
1791
  end
1707
1792
  end
1708
1793
 
1794
+ # Server emoji
1795
+ class Emoji
1796
+ include IDObject
1797
+
1798
+ # @return [String] the emoji name
1799
+ attr_reader :name
1800
+
1801
+ # @return [Server] the server of this emoji
1802
+ attr_reader :server
1803
+
1804
+ # @return [Array<Role>] roles this emoji is active for
1805
+ attr_reader :roles
1806
+
1807
+ def initialize(data, bot, server)
1808
+ @bot = bot
1809
+ @roles = nil
1810
+
1811
+ @name = data['name']
1812
+ @server = server
1813
+ @id = data['id'].to_i
1814
+
1815
+ process_roles(data['roles']) if server
1816
+ end
1817
+
1818
+ # @return [String] the layout to mention it (or have it used) in a message
1819
+ def mention
1820
+ "<:#{@name}:#{@id}>"
1821
+ end
1822
+
1823
+ alias_method :use, :mention
1824
+ alias_method :to_s, :mention
1825
+
1826
+ # @return [String] the icon URL of the emoji
1827
+ def icon_url
1828
+ API.emoji_icon_url(@id)
1829
+ end
1830
+
1831
+ # The inspect method is overwritten to give more useful output
1832
+ def inspect
1833
+ "<Emoji name=#{@name} id=#{@id}>"
1834
+ end
1835
+
1836
+ # @!visibility private
1837
+ def process_roles(roles)
1838
+ @roles = []
1839
+ return unless roles
1840
+ roles.each do |role_id|
1841
+ role = server.role(role_id)
1842
+ @roles << role
1843
+ end
1844
+ end
1845
+ end
1846
+
1847
+ # Emoji that is not tailored to a server
1848
+ class GlobalEmoji
1849
+ include IDObject
1850
+
1851
+ # @return [String] the emoji name
1852
+ attr_reader :name
1853
+
1854
+ # @return [Hash<Integer => Array<Role>>] roles this emoji is active for in every server
1855
+ attr_reader :role_associations
1856
+
1857
+ def initialize(data, bot)
1858
+ @bot = bot
1859
+ @roles = nil
1860
+
1861
+ @name = data.name
1862
+ @id = data.id
1863
+ @role_associations = Hash.new([])
1864
+ @role_associations[data.server.id] = data.roles
1865
+ end
1866
+
1867
+ # @return [String] the layout to mention it (or have it used) in a message
1868
+ def mention
1869
+ "<:#{@name}:#{@id}>"
1870
+ end
1871
+
1872
+ alias_method :use, :mention
1873
+ alias_method :to_s, :mention
1874
+
1875
+ # @return [String] the icon URL of the emoji
1876
+ def icon_url
1877
+ API.emoji_icon_url(@id)
1878
+ end
1879
+
1880
+ # The inspect method is overwritten to give more useful output
1881
+ def inspect
1882
+ "<GlobalEmoji name=#{@name} id=#{@id}>"
1883
+ end
1884
+
1885
+ # @!visibility private
1886
+ def process_roles(roles)
1887
+ new_roles = []
1888
+ return unless roles
1889
+ roles.each do
1890
+ role = server.role(role_id)
1891
+ new_roles << role
1892
+ end
1893
+ new_roles
1894
+ end
1895
+ end
1896
+
1709
1897
  # Basic attributes a server should have
1710
1898
  module ServerAttributes
1711
1899
  # @return [String] this server's name.
@@ -1724,7 +1912,7 @@ module Discordrb
1724
1912
 
1725
1913
  # Integration Account
1726
1914
  class IntegrationAccount
1727
- # @return [String] this accounts's name.
1915
+ # @return [String] this account's name.
1728
1916
  attr_reader :name
1729
1917
 
1730
1918
  # @return [Integer] this account's ID.
@@ -1819,6 +2007,10 @@ module Discordrb
1819
2007
  # @return [Array<Role>] an array of all the roles created on this server.
1820
2008
  attr_reader :roles
1821
2009
 
2010
+ # @return [Array<Emoji>] an array of all the emoji available on this server.
2011
+ attr_reader :emoji
2012
+ alias_method :emojis, :emoji
2013
+
1822
2014
  # @return [true, false] whether or not this server is large (members > 100). If it is,
1823
2015
  # it means the members list may be inaccurate for a couple seconds after starting up the bot.
1824
2016
  attr_reader :large
@@ -1857,8 +2049,10 @@ module Discordrb
1857
2049
  @features = data['features'].map { |element| element.downcase.to_sym }
1858
2050
  @members = {}
1859
2051
  @voice_states = {}
2052
+ @emoji = {}
1860
2053
 
1861
2054
  process_roles(data['roles'])
2055
+ process_emoji(data['emojis'])
1862
2056
  process_members(data['members'])
1863
2057
  process_presences(data['presences'])
1864
2058
  process_channels(data['channels'])
@@ -2172,6 +2366,14 @@ module Discordrb
2172
2366
  update_server_data(afk_timeout: afk_timeout)
2173
2367
  end
2174
2368
 
2369
+ # @return [true, false] whether this server has any emoji or not.
2370
+ def any_emoji?
2371
+ @emoji.any?
2372
+ end
2373
+
2374
+ alias_method :has_emoji?, :any_emoji?
2375
+ alias_method :emoji?, :any_emoji?
2376
+
2175
2377
  # Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
2176
2378
  # @note For internal use only
2177
2379
  # @!visibility private
@@ -2239,6 +2441,14 @@ module Discordrb
2239
2441
  end
2240
2442
  end
2241
2443
 
2444
+ def process_emoji(emoji)
2445
+ return if emoji.empty?
2446
+ emoji.each do |element|
2447
+ new_emoji = Emoji.new(element, @bot, self)
2448
+ @emoji[new_emoji.id] = new_emoji
2449
+ end
2450
+ end
2451
+
2242
2452
  def process_members(members)
2243
2453
  return unless members
2244
2454
  members.each do |element|
@@ -6,7 +6,7 @@ require 'discordrb/data'
6
6
  module Discordrb::Events
7
7
  # Module to make sending messages easier with the presence of a text channel in an event
8
8
  module Respondable
9
- # @return [Channel] the channel in which this event occured
9
+ # @return [Channel] the channel in which this event occurred
10
10
  attr_reader :channel
11
11
 
12
12
  # Sends a message to the channel this message was sent in, right now. It is usually preferable to use {#<<} instead
@@ -609,7 +609,7 @@ module Discordrb
609
609
 
610
610
  # Op 9
611
611
  def handle_invalidate_session
612
- LOGGER.debug('Received op 9, invalidating session and reidentifying.')
612
+ LOGGER.debug('Received op 9, invalidating session and re-identifying.')
613
613
 
614
614
  if @session
615
615
  @session.invalidate
@@ -44,7 +44,7 @@ module Discordrb
44
44
 
45
45
  MODES.each do |mode, hash|
46
46
  define_method(mode) do |message|
47
- write(message, hash) if @enabled_modes.include? mode
47
+ write(message.to_s, hash) if @enabled_modes.include? mode
48
48
  end
49
49
  end
50
50
 
@@ -91,13 +91,17 @@ module Discordrb
91
91
  timestamp = Time.now.strftime(LOG_TIMESTAMP_FORMAT)
92
92
 
93
93
  # Redact token if set
94
- message.gsub!(@token, 'REDACTED_TOKEN') if @token
94
+ log = if @token
95
+ message.to_s.gsub(@token, 'REDACTED_TOKEN')
96
+ else
97
+ message.to_s
98
+ end
95
99
 
96
100
  @streams.each do |stream|
97
101
  if @fancy && !stream.is_a?(File)
98
- fancy_write(stream, message, mode, thread_name, timestamp)
102
+ fancy_write(stream, log, mode, thread_name, timestamp)
99
103
  else
100
- simple_write(stream, message, mode, thread_name, timestamp)
104
+ simple_write(stream, log, mode, thread_name, timestamp)
101
105
  end
102
106
  end
103
107
  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.0.2'.freeze
6
+ VERSION = '3.1.0'.freeze
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discordrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - meew0
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-07 00:00:00.000000000 Z
11
+ date: 2016-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -171,15 +171,6 @@ files:
171
171
  - bin/console
172
172
  - bin/setup
173
173
  - discordrb.gemspec
174
- - examples/commands.rb
175
- - examples/data/music.dca
176
- - examples/data/music.mp3
177
- - examples/eval.rb
178
- - examples/ping.rb
179
- - examples/ping_with_respond_time.rb
180
- - examples/pm_send.rb
181
- - examples/shutdown.rb
182
- - examples/voice_send.rb
183
174
  - lib/discordrb.rb
184
175
  - lib/discordrb/api.rb
185
176
  - lib/discordrb/api/channel.rb
@@ -1,54 +0,0 @@
1
- # This bot has various commands that show off CommandBot.
2
-
3
- require 'discordrb'
4
-
5
- # Here we instantiate a `CommandBot` instead of a regular `Bot`, which has the functionality to add commands using the
6
- # `command` method. We have to set a `prefix` here, which will be the character that triggers command execution.
7
- bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
8
-
9
- bot.command :user do |event|
10
- # Commands send whatever is returned from the block to the channel. This allows for compact commands like this,
11
- # but you have to be aware of this so you don't accidentally return something you didn't intend to.
12
- # To prevent the return value to be sent to the channel, you can just return `nil`.
13
- event.user.name
14
- end
15
-
16
- bot.command :bold do |_event, *args|
17
- # Again, the return value of the block is sent to the channel
18
- "**#{args.join(' ')}**"
19
- end
20
-
21
- bot.command :italic do |_event, *args|
22
- "*#{args.join(' ')}*"
23
- end
24
-
25
- bot.command(:join, permission_level: 1, chain_usable: false) do |event, invite|
26
- event.bot.join invite
27
-
28
- # The `join` call above returns the data Discord sends as a response to joining the server. We don't want that
29
- # in the channel so here we're just returning `nil` afterwards
30
- nil
31
- end
32
-
33
- bot.command(:random, min_args: 0, max_args: 2, description: 'Generates a random number between 0 and 1, 0 and max or min and max.', usage: 'random [min/max] [max]') do |_event, min, max|
34
- # The `if` statement returns one of multiple different things based on the condition. Its return value
35
- # is then returned from the block and sent to the channel
36
- if max
37
- rand(min.to_i..max.to_i)
38
- elsif min
39
- rand(0..min.to_i)
40
- else
41
- rand
42
- end
43
- end
44
-
45
- bot.command :long do |event|
46
- event << 'This is a long message.'
47
- event << 'It has multiple lines that are each sent by doing `event << line`.'
48
- event << 'This is an easy way to do such long messages, or to create lines that should only be sent conditionally.'
49
- event << 'Anyway, have a nice day.'
50
-
51
- # Here we don't have to worry about the return value because the `event << line` statement automatically returns nil.
52
- end
53
-
54
- bot.run
Binary file
Binary file
@@ -1,20 +0,0 @@
1
- # Eval bots are useful for developers because they give you a way to execute code directly from your Discord channel,
2
- # e.g. to quickly check something, demonstrate something to other, or something else entirely. Special care must be
3
- # taken since anyone with access to the command can execute arbitrary code on your system which may potentially be
4
- # malicious.
5
-
6
- require 'discordrb'
7
-
8
- bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
9
-
10
- bot.command(:eval, help_available: false) do |event, *code|
11
- break unless event.user.id == 66237334693085184 # Replace number with your ID
12
-
13
- begin
14
- eval code.join(' ')
15
- rescue
16
- 'An error occured 😞'
17
- end
18
- end
19
-
20
- bot.run
@@ -1,29 +0,0 @@
1
- # This simple bot responds to every "Ping!" message with a "Pong!"
2
-
3
- require 'discordrb'
4
-
5
- # This statement creates a bot with the specified token and application ID. After this line, you can add events to the
6
- # created bot, and eventually run it.
7
- #
8
- # If you don't yet have a token and application ID to put in here, you will need to create a bot account here:
9
- # https://discordapp.com/developers/applications/me
10
- # If you're wondering about what redirect URIs and RPC origins, you can ignore those for now. If that doesn't satisfy
11
- # you, look here: https://github.com/meew0/discordrb/wiki/Redirect-URIs-and-RPC-origins
12
- # After creating the bot, simply copy the token (*not* the OAuth2 secret) and the client ID and put it into the
13
- # respective places.
14
- bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
15
-
16
- # Here we output the invite URL to the console so the bot account can be invited to the channel. This only has to be
17
- # done once, afterwards, you can remove this part if you want
18
- puts "This bot's invite URL is #{bot.invite_url}."
19
- puts 'Click on it to invite it to your server.'
20
-
21
- # This method call adds an event handler that will be called on any message that exactly contains the string "Ping!".
22
- # The code inside it will be executed, and a "Pong!" response will be sent to the channel.
23
- bot.message(content: 'Ping!') do |event|
24
- event.respond 'Pong!'
25
- end
26
-
27
- # This method call has to be put at the end of your script, it is what makes the bot actually connect to Discord. If you
28
- # leave it out (try it!) the script will simply stop and the bot will not appear online.
29
- bot.run
@@ -1,15 +0,0 @@
1
- # This example is nearly the same as the normal ping example, but rather than simply responding with "Pong!", it also
2
- # responds with the time it took to send the message.
3
-
4
- require 'discordrb'
5
-
6
- bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
7
-
8
- bot.message(content: 'Ping!') do |event|
9
- # The `respond` method returns a `Message` object, which is stored in a variable `m`. The `edit` method is then called
10
- # to edit the message with the time difference between when the event was received and after the message was sent.
11
- m = event.respond('Pong!')
12
- m.edit "Pong! Time taken: #{Time.now - event.timestamp} seconds."
13
- end
14
-
15
- bot.run
@@ -1,14 +0,0 @@
1
- # This bot shows off PM functionality by sending a PM every time the bot is mentioned.
2
-
3
- require 'discordrb'
4
-
5
- bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
6
-
7
- # The `mention` event is called if the bot is *directly mentioned*, i.e. not using a role mention or @everyone/@here.
8
- bot.mention do |event|
9
- # The `pm` method is used to send a private message (also called a DM or direct message) to the user who sent the
10
- # initial message.
11
- event.user.pm('You have mentioned me!')
12
- end
13
-
14
- bot.run
@@ -1,19 +0,0 @@
1
- # This bot doesn't do anything except for letting a specifically authorised user shutdown the bot on command.
2
-
3
- require 'discordrb'
4
-
5
- bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
6
-
7
- # Here we can see the `help_available` property used, which can determine whether a command shows up in the default
8
- # generated `help` command. It is true by default but it can be set to false to hide internal commands that only
9
- # specific people can use.
10
- bot.command(:exit, help_available: false) do |event|
11
- # This is a check that only allows a user with a specific ID to execute this command. Otherwise, everyone would be
12
- # able to shut your bot down whenever they wanted.
13
- break unless event.user.id == 66237334693085184 # Replace number with your ID
14
-
15
- bot.send_message(event.channel.id, 'Bot is shutting down')
16
- exit
17
- end
18
-
19
- bot.run
@@ -1,51 +0,0 @@
1
- # discordrb can send music or other audio data to voice channels. This example exists to show that off.
2
- #
3
- # To avoid copyright infringement, the example music I will be using is a self-composed piece of highly debatable
4
- # quality. If you want something better you can replace the files in the data/ directory. Make sure to execute this
5
- # example from the appropriate place, so that it has access to the files in that directory.
6
-
7
- require 'discordrb'
8
-
9
- bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
10
-
11
- bot.command(:connect) do |event|
12
- # The `voice_channel` method returns the voice channel the user is currently in, or `nil` if the user is not in a
13
- # voice channel.
14
- channel = event.user.voice_channel
15
-
16
- # Here we return from the command unless the channel is not nil (i. e. the user is in a voice channel). The `next`
17
- # construct can be used to exit a command prematurely, and even send a message while we're at it.
18
- next "You're not in any voice channel!" unless channel
19
-
20
- # The `voice_connect` method does everything necessary for the bot to connect to a voice channel. Afterwards the bot
21
- # will be connected and ready to play stuff back.
22
- bot.voice_connect(channel)
23
- "Connected to voice channel: #{channel.name}"
24
- end
25
-
26
- # A simple command that plays back an mp3 file.
27
- bot.command(:play_mp3) do |event|
28
- # `event.voice` is a helper method that gets the correct voice bot on the server the bot is currently in. Since a
29
- # bot may be connected to more than one voice channel (never more than one on the same server, though), this is
30
- # necessary to allow the differentiation of servers.
31
- #
32
- # It returns a `VoiceBot` object that methods such as `play_file` can be called on.
33
- voice_bot = event.voice
34
- voice_bot.play_file('data/music.mp3')
35
- end
36
-
37
- # DCA is a custom audio format developed by a couple people from the Discord API community (including myself, meew0).
38
- # It represents the audio data exactly as Discord wants it in a format that is very simple to parse, so libraries can
39
- # very easily add support for it. It has the advantage that absolutely no transcoding has to be done, so it is very
40
- # light on CPU in comparison to `play_file`.
41
- #
42
- # A conversion utility that converts existing audio files to DCA can be found here: https://github.com/nstafie/dca-rs
43
- bot.command(:play_dca) do |event|
44
- voice_bot = event.voice
45
-
46
- # Since the DCA format is non-standard (i.e. ffmpeg doesn't support it), a separate method other than `play_file` has
47
- # to be used to play DCA files back. `play_dca` fulfills that role.
48
- voice_bot.play_dca('data/music.dca')
49
- end
50
-
51
- bot.run