discordrb 3.3.0 → 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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  6. data/.github/pull_request_template.md +37 -0
  7. data/.rubocop.yml +34 -37
  8. data/.travis.yml +5 -6
  9. data/CHANGELOG.md +472 -347
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +61 -79
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +6 -6
  16. data/discordrb.gemspec +17 -17
  17. data/lib/discordrb.rb +73 -0
  18. data/lib/discordrb/allowed_mentions.rb +36 -0
  19. data/lib/discordrb/api.rb +40 -15
  20. data/lib/discordrb/api/channel.rb +57 -39
  21. data/lib/discordrb/api/invite.rb +3 -3
  22. data/lib/discordrb/api/server.rb +55 -50
  23. data/lib/discordrb/api/user.rb +8 -8
  24. data/lib/discordrb/api/webhook.rb +6 -6
  25. data/lib/discordrb/await.rb +0 -1
  26. data/lib/discordrb/bot.rb +164 -72
  27. data/lib/discordrb/cache.rb +4 -2
  28. data/lib/discordrb/colour_rgb.rb +43 -0
  29. data/lib/discordrb/commands/command_bot.rb +22 -6
  30. data/lib/discordrb/commands/container.rb +20 -23
  31. data/lib/discordrb/commands/parser.rb +18 -18
  32. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  33. data/lib/discordrb/container.rb +77 -17
  34. data/lib/discordrb/data.rb +25 -4180
  35. data/lib/discordrb/data/activity.rb +264 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +56 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +849 -0
  40. data/lib/discordrb/data/embed.rb +251 -0
  41. data/lib/discordrb/data/emoji.rb +82 -0
  42. data/lib/discordrb/data/integration.rb +83 -0
  43. data/lib/discordrb/data/invite.rb +137 -0
  44. data/lib/discordrb/data/member.rb +297 -0
  45. data/lib/discordrb/data/message.rb +334 -0
  46. data/lib/discordrb/data/overwrite.rb +102 -0
  47. data/lib/discordrb/data/profile.rb +91 -0
  48. data/lib/discordrb/data/reaction.rb +33 -0
  49. data/lib/discordrb/data/recipient.rb +34 -0
  50. data/lib/discordrb/data/role.rb +191 -0
  51. data/lib/discordrb/data/server.rb +1002 -0
  52. data/lib/discordrb/data/user.rb +204 -0
  53. data/lib/discordrb/data/voice_region.rb +45 -0
  54. data/lib/discordrb/data/voice_state.rb +41 -0
  55. data/lib/discordrb/data/webhook.rb +145 -0
  56. data/lib/discordrb/errors.rb +2 -1
  57. data/lib/discordrb/events/bans.rb +7 -5
  58. data/lib/discordrb/events/channels.rb +2 -0
  59. data/lib/discordrb/events/guilds.rb +16 -9
  60. data/lib/discordrb/events/invites.rb +125 -0
  61. data/lib/discordrb/events/members.rb +6 -2
  62. data/lib/discordrb/events/message.rb +69 -27
  63. data/lib/discordrb/events/presence.rb +14 -4
  64. data/lib/discordrb/events/raw.rb +1 -3
  65. data/lib/discordrb/events/reactions.rb +49 -3
  66. data/lib/discordrb/events/typing.rb +6 -4
  67. data/lib/discordrb/events/voice_server_update.rb +47 -0
  68. data/lib/discordrb/events/voice_state_update.rb +15 -10
  69. data/lib/discordrb/events/webhooks.rb +9 -6
  70. data/lib/discordrb/gateway.rb +72 -57
  71. data/lib/discordrb/id_object.rb +39 -0
  72. data/lib/discordrb/light/integrations.rb +1 -1
  73. data/lib/discordrb/light/light_bot.rb +1 -1
  74. data/lib/discordrb/logger.rb +4 -4
  75. data/lib/discordrb/paginator.rb +57 -0
  76. data/lib/discordrb/permissions.rb +103 -8
  77. data/lib/discordrb/version.rb +1 -1
  78. data/lib/discordrb/voice/encoder.rb +3 -3
  79. data/lib/discordrb/voice/network.rb +84 -43
  80. data/lib/discordrb/voice/sodium.rb +96 -0
  81. data/lib/discordrb/voice/voice_bot.rb +34 -26
  82. metadata +93 -55
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Mixin for the attributes users should have
5
+ module UserAttributes
6
+ # @return [String] this user's username
7
+ attr_reader :username
8
+ alias_method :name, :username
9
+
10
+ # @return [String] this user's discriminator which is used internally to identify users with identical usernames.
11
+ attr_reader :discriminator
12
+ alias_method :discrim, :discriminator
13
+ alias_method :tag, :discriminator
14
+ alias_method :discord_tag, :discriminator
15
+
16
+ # @return [true, false] whether this user is a Discord bot account
17
+ attr_reader :bot_account
18
+ alias_method :bot_account?, :bot_account
19
+
20
+ # @return [String] the ID of this user's current avatar, can be used to generate an avatar URL.
21
+ # @see #avatar_url
22
+ attr_accessor :avatar_id
23
+
24
+ # Utility function to mention users in messages
25
+ # @return [String] the mention code in the form of <@id>
26
+ def mention
27
+ "<@#{@id}>"
28
+ end
29
+
30
+ # Utility function to get Discord's distinct representation of a user, i.e. username + discriminator
31
+ # @return [String] distinct representation of user
32
+ def distinct
33
+ "#{@username}##{@discriminator}"
34
+ end
35
+
36
+ # Utility function to get a user's avatar URL.
37
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the user has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this. Will always be PNG for default avatars.
38
+ # @return [String] the URL to the avatar image.
39
+ def avatar_url(format = nil)
40
+ return API::User.default_avatar(@discriminator) unless @avatar_id
41
+
42
+ API::User.avatar_url(@id, @avatar_id, format)
43
+ end
44
+ end
45
+
46
+ # User on Discord, including internal data like discriminators
47
+ class User
48
+ include IDObject
49
+ include UserAttributes
50
+
51
+ # @return [Symbol] the current online status of the user (`:online`, `:offline` or `:idle`)
52
+ attr_reader :status
53
+
54
+ # @return [ActivitySet] the activities of the user
55
+ attr_reader :activities
56
+
57
+ # @return [Hash<Symbol, Symbol>] the current online status (`:online`, `:idle` or `:dnd`) of the user
58
+ # on various device types (`:desktop`, `:mobile`, or `:web`). The value will be `nil` if the user is offline or invisible.
59
+ attr_reader :client_status
60
+
61
+ # @!visibility private
62
+ def initialize(data, bot)
63
+ @bot = bot
64
+
65
+ @username = data['username']
66
+ @id = data['id'].to_i
67
+ @discriminator = data['discriminator']
68
+ @avatar_id = data['avatar']
69
+ @roles = {}
70
+ @activities = Discordrb::ActivitySet.new
71
+
72
+ @bot_account = false
73
+ @bot_account = true if data['bot']
74
+
75
+ @status = :offline
76
+ @client_status = process_client_status(data['client_status'])
77
+ end
78
+
79
+ # Get a user's PM channel or send them a PM
80
+ # @overload pm
81
+ # Creates a private message channel for this user or returns an existing one if it already exists
82
+ # @return [Channel] the PM channel to this user.
83
+ # @overload pm(content)
84
+ # Sends a private to this user.
85
+ # @param content [String] The content to send.
86
+ # @return [Message] the message sent to this user.
87
+ def pm(content = nil)
88
+ if content
89
+ # Recursively call pm to get the channel, then send a message to it
90
+ channel = pm
91
+ channel.send_message(content)
92
+ else
93
+ # If no message was specified, return the PM channel
94
+ @bot.pm_channel(@id)
95
+ end
96
+ end
97
+
98
+ alias_method :dm, :pm
99
+
100
+ # Send the user a file.
101
+ # @param file [File] The file to send to the user
102
+ # @param caption [String] The caption of the file being sent
103
+ # @param filename [String] Overrides the filename of the uploaded file
104
+ # @param spoiler [true, false] Whether or not this file should appear as a spoiler.
105
+ # @return [Message] the message sent to this user.
106
+ # @example Send a file from disk
107
+ # user.send_file(File.open('rubytaco.png', 'r'))
108
+ def send_file(file, caption = nil, filename: nil, spoiler: nil)
109
+ pm.send_file(file, caption: caption, filename: filename, spoiler: spoiler)
110
+ end
111
+
112
+ # Set the user's name
113
+ # @note for internal use only
114
+ # @!visibility private
115
+ def update_username(username)
116
+ @username = username
117
+ end
118
+
119
+ # Set the user's presence data
120
+ # @note for internal use only
121
+ # @!visibility private
122
+ def update_presence(data)
123
+ @status = data['status'].to_sym
124
+ @client_status = process_client_status(data['client_status'])
125
+
126
+ @activities = Discordrb::ActivitySet.new(data['activities'].map { |act| Activity.new(act, @bot) })
127
+ end
128
+
129
+ # Add an await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
130
+ # user's ID as a :from attribute.
131
+ # @see Bot#add_await
132
+ def await(key, attributes = {}, &block)
133
+ @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
134
+ end
135
+
136
+ # Add a blocking await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
137
+ # user's ID as a :from attribute.
138
+ # @see Bot#add_await!
139
+ def await!(attributes = {}, &block)
140
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
141
+ end
142
+
143
+ # Gets the member this user is on a server
144
+ # @param server [Server] The server to get the member for
145
+ # @return [Member] this user as a member on a particular server
146
+ def on(server)
147
+ id = server.resolve_id
148
+ @bot.server(id).member(@id)
149
+ end
150
+
151
+ # Is the user the bot?
152
+ # @return [true, false] whether this user is the bot
153
+ def current_bot?
154
+ @bot.profile.id == @id
155
+ end
156
+
157
+ # @return [true, false] whether this user is a fake user for a webhook message
158
+ def webhook?
159
+ @discriminator == Message::ZERO_DISCRIM
160
+ end
161
+
162
+ # @!visibility private
163
+ def process_client_status(client_status)
164
+ (client_status || {}).map { |k, v| [k.to_sym, v.to_sym] }.to_h
165
+ end
166
+
167
+ # @!method offline?
168
+ # @return [true, false] whether this user is offline.
169
+ # @!method idle?
170
+ # @return [true, false] whether this user is idle.
171
+ # @!method online?
172
+ # @return [true, false] whether this user is online.
173
+ # @!method dnd?
174
+ # @return [true, false] whether this user is set to do not disturb.
175
+ %i[offline idle online dnd].each do |e|
176
+ define_method("#{e}?") do
177
+ @status.to_sym == e
178
+ end
179
+ end
180
+
181
+ # @return [String, nil] the game the user is currently playing, or `nil` if nothing is being played.
182
+ # @deprecated Please use {ActivitySet#games} for information about the user's game activity
183
+ def game
184
+ @activities.games.first&.name
185
+ end
186
+
187
+ # @return [Integer] returns 1 for twitch streams, or 0 for no stream.
188
+ # @deprecated Please use {ActivitySet#streaming} for information about the user's stream activity
189
+ def stream_type
190
+ @activities.streaming ? 1 : 0
191
+ end
192
+
193
+ # @return [String, nil] the URL to the stream, if the user is currently streaming something
194
+ # @deprecated Please use {ActivitySet#streaming} for information about the user's stream activity
195
+ def stream_url
196
+ @activities.streaming.first&.url
197
+ end
198
+
199
+ # The inspect method is overwritten to give more useful output
200
+ def inspect
201
+ "<User username=#{@username} id=#{@id} discriminator=#{@discriminator}>"
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Voice regions are the locations of servers that handle voice communication in Discord
5
+ class VoiceRegion
6
+ # @return [String] unique ID for the region
7
+ attr_reader :id
8
+ alias_method :to_s, :id
9
+
10
+ # @return [String] name of the region
11
+ attr_reader :name
12
+
13
+ # @return [String] an example hostname for the region
14
+ attr_reader :sample_hostname
15
+
16
+ # @return [Integer] an example port for the region
17
+ attr_reader :sample_port
18
+
19
+ # @return [true, false] if this is a VIP-only server
20
+ attr_reader :vip
21
+
22
+ # @return [true, false] if this voice server is the closest to the client
23
+ attr_reader :optimal
24
+
25
+ # @return [true, false] whether this is a deprecated voice region (avoid switching to these)
26
+ attr_reader :deprecated
27
+
28
+ # @return [true, false] whether this is a custom voice region (used for events/etc)
29
+ attr_reader :custom
30
+
31
+ def initialize(data)
32
+ @id = data['id']
33
+
34
+ @name = data['name']
35
+
36
+ @sample_hostname = data['sample_hostname']
37
+ @sample_port = data['sample_port']
38
+
39
+ @vip = data['vip']
40
+ @optimal = data['optimal']
41
+ @deprecated = data['deprecated']
42
+ @custom = data['custom']
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A voice state represents the state of a member's connection to a voice channel. It includes data like the voice
5
+ # channel the member is connected to and mute/deaf flags.
6
+ class VoiceState
7
+ # @return [Integer] the ID of the user whose voice state is represented by this object.
8
+ attr_reader :user_id
9
+
10
+ # @return [true, false] whether this voice state's member is muted server-wide.
11
+ attr_reader :mute
12
+
13
+ # @return [true, false] whether this voice state's member is deafened server-wide.
14
+ attr_reader :deaf
15
+
16
+ # @return [true, false] whether this voice state's member has muted themselves.
17
+ attr_reader :self_mute
18
+
19
+ # @return [true, false] whether this voice state's member has deafened themselves.
20
+ attr_reader :self_deaf
21
+
22
+ # @return [Channel] the voice channel this voice state's member is in.
23
+ attr_reader :voice_channel
24
+
25
+ # @!visibility private
26
+ def initialize(user_id)
27
+ @user_id = user_id
28
+ end
29
+
30
+ # Update this voice state with new data from Discord
31
+ # @note For internal use only.
32
+ # @!visibility private
33
+ def update(channel, mute, deaf, self_mute, self_deaf)
34
+ @voice_channel = channel
35
+ @mute = mute
36
+ @deaf = deaf
37
+ @self_mute = self_mute
38
+ @self_deaf = self_deaf
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A webhook on a server channel
5
+ class Webhook
6
+ include IDObject
7
+
8
+ # @return [String] the webhook name.
9
+ attr_reader :name
10
+
11
+ # @return [Channel] the channel that the webhook is currently connected to.
12
+ attr_reader :channel
13
+
14
+ # @return [Server] the server that the webhook is currently connected to.
15
+ attr_reader :server
16
+
17
+ # @return [String, nil] the webhook's token, if this is an Incoming Webhook.
18
+ attr_reader :token
19
+
20
+ # @return [String] the webhook's avatar id.
21
+ attr_reader :avatar
22
+
23
+ # @return [Integer] the webhook's type (1: Incoming, 2: Channel Follower)
24
+ attr_reader :type
25
+
26
+ # Gets the user object of the creator of the webhook. May be limited to username, discriminator,
27
+ # ID and avatar if the bot cannot reach the owner
28
+ # @return [Member, User, nil] the user object of the owner or nil if the webhook was requested using the token.
29
+ attr_reader :owner
30
+
31
+ def initialize(data, bot)
32
+ @bot = bot
33
+
34
+ @name = data['name']
35
+ @id = data['id'].to_i
36
+ @channel = bot.channel(data['channel_id'])
37
+ @server = @channel.server
38
+ @token = data['token']
39
+ @avatar = data['avatar']
40
+ @type = data['type']
41
+
42
+ # Will not exist if the data was requested through a webhook token
43
+ return unless data['user']
44
+
45
+ @owner = @server.member(data['user']['id'].to_i)
46
+ return if @owner
47
+
48
+ Discordrb::LOGGER.debug("Member with ID #{data['user']['id']} not cached (possibly left the server).")
49
+ @owner = @bot.ensure_user(data['user'])
50
+ end
51
+
52
+ # Sets the webhook's avatar.
53
+ # @param avatar [String, #read] The new avatar, in base64-encoded JPG format.
54
+ def avatar=(avatar)
55
+ update_webhook(avatar: avatarise(avatar))
56
+ end
57
+
58
+ # Deletes the webhook's avatar.
59
+ def delete_avatar
60
+ update_webhook(avatar: nil)
61
+ end
62
+
63
+ # Sets the webhook's channel
64
+ # @param channel [Channel, String, Integer] The channel the webhook should use.
65
+ def channel=(channel)
66
+ update_webhook(channel_id: channel.resolve_id)
67
+ end
68
+
69
+ # Sets the webhook's name.
70
+ # @param name [String] The webhook's new name.
71
+ def name=(name)
72
+ update_webhook(name: name)
73
+ end
74
+
75
+ # Updates the webhook if you need to edit more than 1 attribute.
76
+ # @param data [Hash] the data to update.
77
+ # @option data [String, #read, nil] :avatar The new avatar, in base64-encoded JPG format, or nil to delete the avatar.
78
+ # @option data [Channel, String, Integer] :channel The channel the webhook should use.
79
+ # @option data [String] :name The webhook's new name.
80
+ # @option data [String] :reason The reason for the webhook changes.
81
+ def update(data)
82
+ # Only pass a value for avatar if the key is defined as sending nil will delete the
83
+ data[:avatar] = avatarise(data[:avatar]) if data.key?(:avatar)
84
+ data[:channel_id] = data[:channel].resolve_id
85
+ data.delete(:channel)
86
+ update_webhook(data)
87
+ end
88
+
89
+ # Deletes the webhook.
90
+ # @param reason [String] The reason the invite is being deleted.
91
+ def delete(reason = nil)
92
+ if token?
93
+ API::Webhook.token_delete_webhook(@token, @id, reason)
94
+ else
95
+ API::Webhook.delete_webhook(@bot.token, @id, reason)
96
+ end
97
+ end
98
+
99
+ # Utility function to get a webhook's avatar URL.
100
+ # @return [String] the URL to the avatar image
101
+ def avatar_url
102
+ return API::User.default_avatar unless @avatar
103
+
104
+ API::User.avatar_url(@id, @avatar)
105
+ end
106
+
107
+ # The `inspect` method is overwritten to give more useful output.
108
+ def inspect
109
+ "<Webhook name=#{@name} id=#{@id}>"
110
+ end
111
+
112
+ # Utility function to know if the webhook was requested through a webhook token, rather than auth.
113
+ # @return [true, false] whether the webhook was requested by token or not.
114
+ def token?
115
+ @owner.nil?
116
+ end
117
+
118
+ private
119
+
120
+ def avatarise(avatar)
121
+ if avatar.respond_to? :read
122
+ "data:image/jpg;base64,#{Base64.strict_encode64(avatar.read)}"
123
+ else
124
+ avatar
125
+ end
126
+ end
127
+
128
+ def update_internal(data)
129
+ @name = data['name']
130
+ @avatar_id = data['avatar']
131
+ @channel = @bot.channel(data['channel_id'])
132
+ end
133
+
134
+ def update_webhook(new_data)
135
+ reason = new_data.delete(:reason)
136
+ data = JSON.parse(if token?
137
+ API::Webhook.token_update_webhook(@token, @id, new_data, reason)
138
+ else
139
+ API::Webhook.update_webhook(@bot.token, @id, new_data, reason)
140
+ end)
141
+ # Only update cache if API call worked
142
+ update_internal(data) if data['name']
143
+ end
144
+ end
145
+ end
@@ -43,7 +43,7 @@ module Discordrb
43
43
  end
44
44
 
45
45
  # Create a new code error class
46
- # rubocop:disable Style/MethodName
46
+ # rubocop:disable Naming/MethodName
47
47
  def self.Code(code)
48
48
  classy = Class.new(CodeError)
49
49
  classy.instance_variable_set('@code', code)
@@ -53,6 +53,7 @@ module Discordrb
53
53
 
54
54
  classy
55
55
  end
56
+ # rubocop:enable Naming/MethodName
56
57
 
57
58
  # @param code [Integer] The code to check
58
59
  # @return [Class] the error class for the given code