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,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