discordrb 3.3.0 → 3.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  5. data/.github/pull_request_template.md +37 -0
  6. data/.rubocop.yml +34 -37
  7. data/.travis.yml +5 -6
  8. data/CHANGELOG.md +504 -347
  9. data/Gemfile +2 -0
  10. data/LICENSE.txt +1 -1
  11. data/README.md +61 -79
  12. data/Rakefile +2 -0
  13. data/bin/console +1 -0
  14. data/discordrb-webhooks.gemspec +6 -6
  15. data/discordrb.gemspec +18 -18
  16. data/lib/discordrb/allowed_mentions.rb +36 -0
  17. data/lib/discordrb/api/channel.rb +62 -39
  18. data/lib/discordrb/api/invite.rb +3 -3
  19. data/lib/discordrb/api/server.rb +57 -50
  20. data/lib/discordrb/api/user.rb +9 -8
  21. data/lib/discordrb/api/webhook.rb +6 -6
  22. data/lib/discordrb/api.rb +40 -15
  23. data/lib/discordrb/await.rb +0 -1
  24. data/lib/discordrb/bot.rb +175 -73
  25. data/lib/discordrb/cache.rb +4 -2
  26. data/lib/discordrb/colour_rgb.rb +43 -0
  27. data/lib/discordrb/commands/command_bot.rb +30 -9
  28. data/lib/discordrb/commands/container.rb +20 -23
  29. data/lib/discordrb/commands/parser.rb +18 -18
  30. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  31. data/lib/discordrb/container.rb +77 -17
  32. data/lib/discordrb/data/activity.rb +271 -0
  33. data/lib/discordrb/data/application.rb +50 -0
  34. data/lib/discordrb/data/attachment.rb +56 -0
  35. data/lib/discordrb/data/audit_logs.rb +345 -0
  36. data/lib/discordrb/data/channel.rb +849 -0
  37. data/lib/discordrb/data/embed.rb +251 -0
  38. data/lib/discordrb/data/emoji.rb +82 -0
  39. data/lib/discordrb/data/integration.rb +83 -0
  40. data/lib/discordrb/data/invite.rb +137 -0
  41. data/lib/discordrb/data/member.rb +297 -0
  42. data/lib/discordrb/data/message.rb +334 -0
  43. data/lib/discordrb/data/overwrite.rb +102 -0
  44. data/lib/discordrb/data/profile.rb +91 -0
  45. data/lib/discordrb/data/reaction.rb +33 -0
  46. data/lib/discordrb/data/recipient.rb +34 -0
  47. data/lib/discordrb/data/role.rb +191 -0
  48. data/lib/discordrb/data/server.rb +1002 -0
  49. data/lib/discordrb/data/user.rb +204 -0
  50. data/lib/discordrb/data/voice_region.rb +45 -0
  51. data/lib/discordrb/data/voice_state.rb +41 -0
  52. data/lib/discordrb/data/webhook.rb +145 -0
  53. data/lib/discordrb/data.rb +25 -4180
  54. data/lib/discordrb/errors.rb +2 -1
  55. data/lib/discordrb/events/bans.rb +7 -5
  56. data/lib/discordrb/events/channels.rb +2 -0
  57. data/lib/discordrb/events/guilds.rb +16 -9
  58. data/lib/discordrb/events/invites.rb +125 -0
  59. data/lib/discordrb/events/members.rb +6 -2
  60. data/lib/discordrb/events/message.rb +69 -27
  61. data/lib/discordrb/events/presence.rb +14 -4
  62. data/lib/discordrb/events/raw.rb +1 -3
  63. data/lib/discordrb/events/reactions.rb +49 -3
  64. data/lib/discordrb/events/typing.rb +6 -4
  65. data/lib/discordrb/events/voice_server_update.rb +47 -0
  66. data/lib/discordrb/events/voice_state_update.rb +15 -10
  67. data/lib/discordrb/events/webhooks.rb +9 -6
  68. data/lib/discordrb/gateway.rb +72 -57
  69. data/lib/discordrb/id_object.rb +39 -0
  70. data/lib/discordrb/light/integrations.rb +1 -1
  71. data/lib/discordrb/light/light_bot.rb +1 -1
  72. data/lib/discordrb/logger.rb +4 -4
  73. data/lib/discordrb/paginator.rb +57 -0
  74. data/lib/discordrb/permissions.rb +103 -8
  75. data/lib/discordrb/version.rb +1 -1
  76. data/lib/discordrb/voice/encoder.rb +16 -7
  77. data/lib/discordrb/voice/network.rb +84 -43
  78. data/lib/discordrb/voice/sodium.rb +96 -0
  79. data/lib/discordrb/voice/voice_bot.rb +34 -26
  80. data/lib/discordrb.rb +73 -0
  81. metadata +98 -60
  82. /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A permissions overwrite, when applied to channels describes additional
5
+ # permissions a member needs to perform certain actions in context.
6
+ class Overwrite
7
+ # @return [Integer] ID of the thing associated with this overwrite type
8
+ attr_accessor :id
9
+
10
+ # @return [Symbol] either :role or :member
11
+ attr_accessor :type
12
+
13
+ # @return [Permissions] allowed permissions for this overwrite type
14
+ attr_accessor :allow
15
+
16
+ # @return [Permissions] denied permissions for this overwrite type
17
+ attr_accessor :deny
18
+
19
+ # Creates a new Overwrite object
20
+ # @example Create an overwrite for a role that can mention everyone, send TTS messages, but can't create instant invites
21
+ # allow = Discordrb::Permissions.new
22
+ # allow.can_mention_everyone = true
23
+ # allow.can_send_tts_messages = true
24
+ #
25
+ # deny = Discordrb::Permissions.new
26
+ # deny.can_create_instant_invite = true
27
+ #
28
+ # # Find some role by name
29
+ # role = server.roles.find { |r| r.name == 'some role' }
30
+ #
31
+ # Overwrite.new(role, allow: allow, deny: deny)
32
+ # @example Create an overwrite by ID and permissions bits
33
+ # Overwrite.new(120571255635181568, type: 'member', allow: 1024, deny: 0)
34
+ # @param object [Integer, #id] the ID or object this overwrite is for
35
+ # @param type [String] the type of object this overwrite is for (only required if object is an Integer)
36
+ # @param allow [Integer, Permissions] allowed permissions for this overwrite, by bits or a Permissions object
37
+ # @param deny [Integer, Permissions] denied permissions for this overwrite, by bits or a Permissions object
38
+ # @raise [ArgumentError] if type is not :member or :role
39
+ def initialize(object = nil, type: nil, allow: 0, deny: 0)
40
+ if type
41
+ type = type.to_sym
42
+ raise ArgumentError, 'Overwrite type must be :member or :role' unless (type != :member) || (type != :role)
43
+ end
44
+
45
+ @id = object.respond_to?(:id) ? object.id : object
46
+
47
+ @type = case object
48
+ when User, Member, Recipient, Profile
49
+ :member
50
+ when Role
51
+ :role
52
+ else
53
+ type
54
+ end
55
+
56
+ @allow = allow.is_a?(Permissions) ? allow : Permissions.new(allow)
57
+ @deny = deny.is_a?(Permissions) ? deny : Permissions.new(deny)
58
+ end
59
+
60
+ # Comparison by attributes [:id, :type, :allow, :deny]
61
+ def ==(other)
62
+ false unless other.is_a? Discordrb::Overwrite
63
+ id == other.id &&
64
+ type == other.type &&
65
+ allow == other.allow &&
66
+ deny == other.deny
67
+ end
68
+
69
+ # @return [Overwrite] create an overwrite from a hash payload
70
+ # @!visibility private
71
+ def self.from_hash(data)
72
+ new(
73
+ data['id'].to_i,
74
+ type: data['type'],
75
+ allow: Permissions.new(data['allow']),
76
+ deny: Permissions.new(data['deny'])
77
+ )
78
+ end
79
+
80
+ # @return [Overwrite] copies an overwrite from another Overwrite
81
+ # @!visibility private
82
+ def self.from_other(other)
83
+ new(
84
+ other.id,
85
+ type: other.type,
86
+ allow: Permissions.new(other.allow.bits),
87
+ deny: Permissions.new(other.deny.bits)
88
+ )
89
+ end
90
+
91
+ # @return [Hash] hash representation of an overwrite
92
+ # @!visibility private
93
+ def to_hash
94
+ {
95
+ id: id,
96
+ type: type,
97
+ allow: allow.bits,
98
+ deny: deny.bits
99
+ }
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # This class is a special variant of User that represents the bot's user profile (things like own username and the avatar).
5
+ # It can be accessed using {Bot#profile}.
6
+ class Profile < User
7
+ # Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
8
+ # @return [true]
9
+ def current_bot?
10
+ true
11
+ end
12
+
13
+ # Sets the bot's username.
14
+ # @param username [String] The new username.
15
+ def username=(username)
16
+ update_profile_data(username: username)
17
+ end
18
+
19
+ alias_method :name=, :username=
20
+
21
+ # Changes the bot's avatar.
22
+ # @param avatar [String, #read] A JPG file to be used as the avatar, either
23
+ # something readable (e.g. File Object) or as a data URL.
24
+ def avatar=(avatar)
25
+ if avatar.respond_to? :read
26
+ # Set the file to binary mode if supported, so we don't get problems with Windows
27
+ avatar.binmode if avatar.respond_to?(:binmode)
28
+
29
+ avatar_string = 'data:image/jpg;base64,'
30
+ avatar_string += Base64.strict_encode64(avatar.read)
31
+ update_profile_data(avatar: avatar_string)
32
+ else
33
+ update_profile_data(avatar: avatar)
34
+ end
35
+ end
36
+
37
+ # Updates the cached profile data with the new one.
38
+ # @note For internal use only.
39
+ # @!visibility private
40
+ def update_data(new_data)
41
+ @username = new_data[:username] || @username
42
+ @avatar_id = new_data[:avatar_id] || @avatar_id
43
+ end
44
+
45
+ # Sets the user status setting to Online.
46
+ # @note Only usable on User accounts.
47
+ def online
48
+ update_profile_status_setting('online')
49
+ end
50
+
51
+ # Sets the user status setting to Idle.
52
+ # @note Only usable on User accounts.
53
+ def idle
54
+ update_profile_status_setting('idle')
55
+ end
56
+
57
+ # Sets the user status setting to Do Not Disturb.
58
+ # @note Only usable on User accounts.
59
+ def dnd
60
+ update_profile_status_setting('dnd')
61
+ end
62
+
63
+ alias_method(:busy, :dnd)
64
+
65
+ # Sets the user status setting to Invisible.
66
+ # @note Only usable on User accounts.
67
+ def invisible
68
+ update_profile_status_setting('invisible')
69
+ end
70
+
71
+ # The inspect method is overwritten to give more useful output
72
+ def inspect
73
+ "<Profile user=#{super}>"
74
+ end
75
+
76
+ private
77
+
78
+ # Internal handler for updating the user's status setting
79
+ def update_profile_status_setting(status)
80
+ API::User.change_status_setting(@bot.token, status)
81
+ end
82
+
83
+ def update_profile_data(new_data)
84
+ API::User.update_profile(@bot.token,
85
+ nil, nil,
86
+ new_data[:username] || @username,
87
+ new_data.key?(:avatar) ? new_data[:avatar] : @avatar_id)
88
+ update_data(new_data)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A reaction to a message.
5
+ class Reaction
6
+ # @return [Integer] the amount of users who have reacted with this reaction
7
+ attr_reader :count
8
+
9
+ # @return [true, false] whether the current bot or user used this reaction
10
+ attr_reader :me
11
+ alias_method :me?, :me
12
+
13
+ # @return [Integer] the ID of the emoji, if it was custom
14
+ attr_reader :id
15
+
16
+ # @return [String] the name or unicode representation of the emoji
17
+ attr_reader :name
18
+
19
+ def initialize(data)
20
+ @count = data['count']
21
+ @me = data['me']
22
+ @id = data['emoji']['id'].nil? ? nil : data['emoji']['id'].to_i
23
+ @name = data['emoji']['name']
24
+ end
25
+
26
+ # Converts this Reaction into a string that can be sent back to Discord in other reaction endpoints.
27
+ # If ID is present, it will be rendered into the form of `name:id`.
28
+ # @return [String] the name of this reaction, including the ID if it is a custom emoji
29
+ def to_s
30
+ id.nil? ? name : "#{name}:#{id}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Recipients are members on private channels - they exist for completeness purposes, but all
5
+ # the attributes will be empty.
6
+ class Recipient < DelegateClass(User)
7
+ include MemberAttributes
8
+
9
+ # @return [Channel] the private channel this recipient is the recipient of.
10
+ attr_reader :channel
11
+
12
+ # @!visibility private
13
+ def initialize(user, channel, bot)
14
+ @bot = bot
15
+ @channel = channel
16
+ raise ArgumentError, 'Tried to create a recipient for a public channel!' unless @channel.private?
17
+
18
+ @user = user
19
+ super @user
20
+
21
+ # Member attributes
22
+ @mute = @deaf = @self_mute = @self_deaf = false
23
+ @voice_channel = nil
24
+ @server = nil
25
+ @roles = []
26
+ @joined_at = @channel.creation_time
27
+ end
28
+
29
+ # Overwriting inspect for debug purposes
30
+ def inspect
31
+ "<Recipient user=#{@user.inspect} channel=#{@channel.inspect}>"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A Discord role that contains permissions and applies to certain users
5
+ class Role
6
+ include IDObject
7
+
8
+ # @return [Permissions] this role's permissions.
9
+ attr_reader :permissions
10
+
11
+ # @return [String] this role's name ("new role" if it hasn't been changed)
12
+ attr_reader :name
13
+
14
+ # @return [Server] the server this role belongs to
15
+ attr_reader :server
16
+
17
+ # @return [true, false] whether or not this role should be displayed separately from other users
18
+ attr_reader :hoist
19
+
20
+ # @return [true, false] whether or not this role is managed by an integration or a bot
21
+ attr_reader :managed
22
+ alias_method :managed?, :managed
23
+
24
+ # @return [true, false] whether this role can be mentioned using a role mention
25
+ attr_reader :mentionable
26
+ alias_method :mentionable?, :mentionable
27
+
28
+ # @return [ColourRGB] the role colour
29
+ attr_reader :colour
30
+ alias_method :color, :colour
31
+
32
+ # @return [Integer] the position of this role in the hierarchy
33
+ attr_reader :position
34
+
35
+ # This class is used internally as a wrapper to a Role object that allows easy writing of permission data.
36
+ class RoleWriter
37
+ # @!visibility private
38
+ def initialize(role, token)
39
+ @role = role
40
+ @token = token
41
+ end
42
+
43
+ # Write the specified permission data to the role, without updating the permission cache
44
+ # @param bits [Integer] The packed permissions to write.
45
+ def write(bits)
46
+ @role.send(:packed=, bits, false)
47
+ end
48
+
49
+ # The inspect method is overridden, in this case to prevent the token being leaked
50
+ def inspect
51
+ "<RoleWriter role=#{@role} token=...>"
52
+ end
53
+ end
54
+
55
+ # @!visibility private
56
+ def initialize(data, bot, server = nil)
57
+ @bot = bot
58
+ @server = server
59
+ @permissions = Permissions.new(data['permissions'], RoleWriter.new(self, @bot.token))
60
+ @name = data['name']
61
+ @id = data['id'].to_i
62
+
63
+ @position = data['position']
64
+
65
+ @hoist = data['hoist']
66
+ @mentionable = data['mentionable']
67
+ @managed = data['managed']
68
+
69
+ @colour = ColourRGB.new(data['color'])
70
+ end
71
+
72
+ # @return [String] a string that will mention this role, if it is mentionable.
73
+ def mention
74
+ "<@&#{@id}>"
75
+ end
76
+
77
+ # @return [Array<Member>] an array of members who have this role.
78
+ # @note This requests a member chunk if it hasn't for the server before, which may be slow initially
79
+ def members
80
+ @server.members.select { |m| m.role? self }
81
+ end
82
+
83
+ alias_method :users, :members
84
+
85
+ # Updates the data cache from another Role object
86
+ # @note For internal use only
87
+ # @!visibility private
88
+ def update_from(other)
89
+ @permissions = other.permissions
90
+ @name = other.name
91
+ @hoist = other.hoist
92
+ @colour = other.colour
93
+ @position = other.position
94
+ @managed = other.managed
95
+ end
96
+
97
+ # Updates the data cache from a hash containing data
98
+ # @note For internal use only
99
+ # @!visibility private
100
+ def update_data(new_data)
101
+ @name = new_data[:name] || new_data['name'] || @name
102
+ @hoist = new_data['hoist'] unless new_data['hoist'].nil?
103
+ @hoist = new_data[:hoist] unless new_data[:hoist].nil?
104
+ @colour = new_data[:colour] || (new_data['color'] ? ColourRGB.new(new_data['color']) : @colour)
105
+ end
106
+
107
+ # Sets the role name to something new
108
+ # @param name [String] The name that should be set
109
+ def name=(name)
110
+ update_role_data(name: name)
111
+ end
112
+
113
+ # Changes whether or not this role is displayed at the top of the user list
114
+ # @param hoist [true, false] The value it should be changed to
115
+ def hoist=(hoist)
116
+ update_role_data(hoist: hoist)
117
+ end
118
+
119
+ # Changes whether or not this role can be mentioned
120
+ # @param mentionable [true, false] The value it should be changed to
121
+ def mentionable=(mentionable)
122
+ update_role_data(mentionable: mentionable)
123
+ end
124
+
125
+ # Sets the role colour to something new
126
+ # @param colour [ColourRGB] The new colour
127
+ def colour=(colour)
128
+ update_role_data(colour: colour)
129
+ end
130
+
131
+ alias_method :color=, :colour=
132
+
133
+ # Changes this role's permissions to a fixed bitfield. This allows setting multiple permissions at once with just
134
+ # one API call.
135
+ #
136
+ # Information on how this bitfield is structured can be found at
137
+ # https://discord.com/developers/docs/topics/permissions.
138
+ # @example Remove all permissions from a role
139
+ # role.packed = 0
140
+ # @param packed [Integer] A bitfield with the desired permissions value.
141
+ # @param update_perms [true, false] Whether the internal data should also be updated. This should always be true
142
+ # when calling externally.
143
+ def packed=(packed, update_perms = true)
144
+ update_role_data(permissions: packed)
145
+ @permissions.bits = packed if update_perms
146
+ end
147
+
148
+ # Moves this role above another role in the list.
149
+ # @param other [Role, String, Integer, nil] The role, or its ID, above which this role should be moved. If it is `nil`,
150
+ # the role will be moved above the @everyone role.
151
+ # @return [Integer] the new position of this role
152
+ def sort_above(other = nil)
153
+ other = @server.role(other.resolve_id) if other
154
+ roles = @server.roles.sort_by(&:position)
155
+ roles.delete_at(@position)
156
+
157
+ index = other ? roles.index { |role| role.id == other.id } + 1 : 1
158
+ roles.insert(index, self)
159
+
160
+ updated_roles = roles.map.with_index { |role, position| { id: role.id, position: position } }
161
+ @server.update_role_positions(updated_roles)
162
+ index
163
+ end
164
+
165
+ alias_method :move_above, :sort_above
166
+
167
+ # Deletes this role. This cannot be undone without recreating the role!
168
+ # @param reason [String] the reason for this role's deletion
169
+ def delete(reason = nil)
170
+ API::Server.delete_role(@bot.token, @server.id, @id, reason)
171
+ @server.delete_role(@id)
172
+ end
173
+
174
+ # The inspect method is overwritten to give more useful output
175
+ def inspect
176
+ "<Role name=#{@name} permissions=#{@permissions.inspect} hoist=#{@hoist} colour=#{@colour.inspect} server=#{@server.inspect} position=#{@position} mentionable=#{@mentionable}>"
177
+ end
178
+
179
+ private
180
+
181
+ def update_role_data(new_data)
182
+ API::Server.update_role(@bot.token, @server.id, @id,
183
+ new_data[:name] || @name,
184
+ (new_data[:colour] || @colour).combined,
185
+ new_data[:hoist].nil? ? @hoist : new_data[:hoist],
186
+ new_data[:mentionable].nil? ? @mentionable : new_data[:mentionable],
187
+ new_data[:permissions] || @permissions.bits)
188
+ update_data(new_data)
189
+ end
190
+ end
191
+ end