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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +126 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- data/.github/pull_request_template.md +37 -0
- data/.rubocop.yml +34 -37
- data/.travis.yml +5 -6
- data/CHANGELOG.md +472 -347
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +61 -79
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +6 -6
- data/discordrb.gemspec +17 -17
- data/lib/discordrb.rb +73 -0
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api.rb +40 -15
- data/lib/discordrb/api/channel.rb +57 -39
- data/lib/discordrb/api/invite.rb +3 -3
- data/lib/discordrb/api/server.rb +55 -50
- data/lib/discordrb/api/user.rb +8 -8
- data/lib/discordrb/api/webhook.rb +6 -6
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +164 -72
- data/lib/discordrb/cache.rb +4 -2
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +22 -6
- data/lib/discordrb/commands/container.rb +20 -23
- data/lib/discordrb/commands/parser.rb +18 -18
- data/lib/discordrb/commands/rate_limiter.rb +3 -2
- data/lib/discordrb/container.rb +77 -17
- data/lib/discordrb/data.rb +25 -4180
- data/lib/discordrb/data/activity.rb +264 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +56 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +849 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +83 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +297 -0
- data/lib/discordrb/data/message.rb +334 -0
- data/lib/discordrb/data/overwrite.rb +102 -0
- data/lib/discordrb/data/profile.rb +91 -0
- data/lib/discordrb/data/reaction.rb +33 -0
- data/lib/discordrb/data/recipient.rb +34 -0
- data/lib/discordrb/data/role.rb +191 -0
- data/lib/discordrb/data/server.rb +1002 -0
- data/lib/discordrb/data/user.rb +204 -0
- data/lib/discordrb/data/voice_region.rb +45 -0
- data/lib/discordrb/data/voice_state.rb +41 -0
- data/lib/discordrb/data/webhook.rb +145 -0
- data/lib/discordrb/errors.rb +2 -1
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +2 -0
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +69 -27
- data/lib/discordrb/events/presence.rb +14 -4
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -3
- data/lib/discordrb/events/typing.rb +6 -4
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +15 -10
- data/lib/discordrb/events/webhooks.rb +9 -6
- data/lib/discordrb/gateway.rb +72 -57
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +4 -4
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +103 -8
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +3 -3
- data/lib/discordrb/voice/network.rb +84 -43
- data/lib/discordrb/voice/sodium.rb +96 -0
- data/lib/discordrb/voice/voice_bot.rb +34 -26
- metadata +93 -55
@@ -0,0 +1,264 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# Contains information about user activities such as the game they are playing,
|
5
|
+
# music they are listening to, or their live stream.
|
6
|
+
class Activity
|
7
|
+
# Values corresponding to the flags bitmask
|
8
|
+
FLAGS = {
|
9
|
+
instance: 1 << 0, # this activity is an instanced game session
|
10
|
+
join: 1 << 1, # this activity is joinable
|
11
|
+
spectate: 1 << 2, # this activity can be spectated
|
12
|
+
join_request: 1 << 3, # this activity allows asking to join
|
13
|
+
sync: 1 << 4, # this activity is a spotify track
|
14
|
+
play: 1 << 5 # this game can be played or opened from discord
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# @return [String] the activity's name
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
# @return [Integer, nil] activity type. Can be {GAME}, {STREAMING}, {LISTENING}, {CUSTOM}
|
21
|
+
attr_reader :type
|
22
|
+
|
23
|
+
# @return [String, nil] stream URL, when the activity type is {STREAMING}
|
24
|
+
attr_reader :url
|
25
|
+
|
26
|
+
# @return [String, nil] the application ID for the game
|
27
|
+
attr_reader :application_id
|
28
|
+
|
29
|
+
# @return [String, nil] details about what the player is currently doing
|
30
|
+
attr_reader :details
|
31
|
+
|
32
|
+
# @return [String, nil] the user's current party status
|
33
|
+
attr_reader :state
|
34
|
+
|
35
|
+
# @return [true, false] whether or not the activity is an instanced game session
|
36
|
+
attr_reader :instance
|
37
|
+
|
38
|
+
# @return [Integer] a bitmask of activity flags
|
39
|
+
# @see FLAGS
|
40
|
+
attr_reader :flags
|
41
|
+
|
42
|
+
# @return [Timestamps, nil] times for the start and/or end of the activity
|
43
|
+
attr_reader :timestamps
|
44
|
+
|
45
|
+
# @return [Secrets, nil] secrets for rich presence, joining, and spectating
|
46
|
+
attr_reader :secrets
|
47
|
+
|
48
|
+
# @return [Assets, nil] images for the presence and their texts
|
49
|
+
attr_reader :assets
|
50
|
+
|
51
|
+
# @return [Party, nil] information about the player's current party
|
52
|
+
attr_reader :party
|
53
|
+
|
54
|
+
# @return [Emoji, nil] emoji data for custom statuses
|
55
|
+
attr_reader :emoji
|
56
|
+
|
57
|
+
# @return [Time] the time when the activity was added to the user's session
|
58
|
+
attr_reader :created_at
|
59
|
+
|
60
|
+
# Type indicating the activity is for a game
|
61
|
+
GAME = 0
|
62
|
+
# Type indicating the activity is a stream
|
63
|
+
STREAMING = 1
|
64
|
+
# Type indicating the activity is for music
|
65
|
+
LISTENING = 2
|
66
|
+
# This type is currently unused in the client but can be reported by bots
|
67
|
+
WATCHING = 3
|
68
|
+
# Type indicating the activity is a custom status
|
69
|
+
CUSTOM = 4
|
70
|
+
|
71
|
+
# @!visibility private
|
72
|
+
def initialize(data, bot)
|
73
|
+
@name = data['name']
|
74
|
+
@type = data['type']
|
75
|
+
@url = data['url']
|
76
|
+
@application_id = data['application_id']
|
77
|
+
@details = data['details']
|
78
|
+
@state = data['state']
|
79
|
+
@instance = data['instance']
|
80
|
+
@flags = data['flags'] || 0
|
81
|
+
@created_at = Time.at(data['created_at'].to_i)
|
82
|
+
|
83
|
+
@timestamps = Timestamps.new(data['timestamps']) if data['timestamps']
|
84
|
+
@secrets = Secret.new(data['secrets']) if data['secrets']
|
85
|
+
@assets = Assets.new(data['assets'], @application_id) if data['assets']
|
86
|
+
@party = Party.new(data['party']) if data['party']
|
87
|
+
@emoji = Emoji.new(data['emoji'], bot, nil) if data['emoji']
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [true, false] Whether or not the `join` flag is set for this activity
|
91
|
+
def join?
|
92
|
+
flag_set? :join
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [true, false] Whether or not the `spectate` flag is set for this activity
|
96
|
+
def spectate?
|
97
|
+
flag_set? :spectate
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [true, false] Whether or not the `join_request` flag is set for this activity
|
101
|
+
def join_request?
|
102
|
+
flag_set? :join_request
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [true, false] Whether or not the `sync` flag is set for this activity
|
106
|
+
def sync?
|
107
|
+
flag_set? :sync
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [true, false] Whether or not the `play` flag is set for this activity
|
111
|
+
def play?
|
112
|
+
flag_set? :play
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [true, false] Whether or not the `instance` flag is set for this activity
|
116
|
+
def instance?
|
117
|
+
@instance || flag_set?(:instance)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!visibility private
|
121
|
+
def flag_set?(sym)
|
122
|
+
!(@flags & FLAGS[sym]).zero?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Timestamps for the start and end of instanced activities
|
126
|
+
class Timestamps
|
127
|
+
# @return [Time, nil]
|
128
|
+
attr_reader :start
|
129
|
+
|
130
|
+
# @return [Time, nil]
|
131
|
+
attr_reader :end
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
def initialize(data)
|
135
|
+
@start = Time.at(data['start'] / 1000) if data['start']
|
136
|
+
@end = Time.at(data['end'] / 1000) if data['end']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Contains secrets used for rich presence
|
141
|
+
class Secrets
|
142
|
+
# @return [String, nil] secret for joining a party
|
143
|
+
attr_reader :join
|
144
|
+
|
145
|
+
# @return [String, nil] secret for spectating
|
146
|
+
attr_reader :spectate
|
147
|
+
|
148
|
+
# @return [String, nil] secret for a specific instanced match
|
149
|
+
attr_reader :match
|
150
|
+
|
151
|
+
# @!visibility private
|
152
|
+
def initialize(data)
|
153
|
+
@join = data['join']
|
154
|
+
@spectate = data['spectate']
|
155
|
+
@match = data['match']
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Assets for rich presence images and hover text
|
160
|
+
class Assets
|
161
|
+
# @return [String, nil] the asset ID for the large image of this activity
|
162
|
+
attr_reader :large_image_id
|
163
|
+
|
164
|
+
# @return [String, nil] text displayed when hovering over the large iamge
|
165
|
+
attr_reader :large_text
|
166
|
+
|
167
|
+
# @return [String, nil] the asset ID for the small image of this activity
|
168
|
+
attr_reader :small_image_id
|
169
|
+
|
170
|
+
# @return [String, nil]
|
171
|
+
attr_reader :small_text
|
172
|
+
|
173
|
+
# @return [String, nil] the application ID for these assets.
|
174
|
+
attr_reader :application_id
|
175
|
+
|
176
|
+
# @!visibility private
|
177
|
+
def initialize(data, application_id)
|
178
|
+
@application_id = application_id
|
179
|
+
@large_image_id = data['large_image']
|
180
|
+
@large_text = data['large_text']
|
181
|
+
@small_image_id = data['small_image']
|
182
|
+
@small_text = data['small_text']
|
183
|
+
end
|
184
|
+
|
185
|
+
# Utility function to get an Asset's large image URL.
|
186
|
+
# @param format [String, nil] If `nil`, the URL will default to `webp`. You can otherwise specify one of `webp`, `jpg`, or `png`.
|
187
|
+
# @return [String] the URL to the large image asset.
|
188
|
+
def large_image_url(format = 'webp')
|
189
|
+
API.asset_url(@application_id, @large_image_id, format)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Utility function to get an Asset's large image URL.
|
193
|
+
# @param format [String, nil] If `nil`, the URL will default to `webp`. You can otherwise specify one of `webp`, `jpg`, or `png`.
|
194
|
+
# @return [String] the URL to the small image asset.
|
195
|
+
def small_image_url(format = 'webp')
|
196
|
+
API.asset_url(@application_id, @small_image_id, format)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Contains information about an activity's party
|
201
|
+
class Party
|
202
|
+
# @return [String, nil]
|
203
|
+
attr_reader :id
|
204
|
+
|
205
|
+
# @return [Integer, nil]
|
206
|
+
attr_reader :current_size
|
207
|
+
|
208
|
+
# @return [Integer, nil]
|
209
|
+
attr_reader :max_size
|
210
|
+
|
211
|
+
# @!visibility private
|
212
|
+
def initialize(data)
|
213
|
+
@id = data['id']
|
214
|
+
@current_size, @max_size = data['size']
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# A collection of the user's activities.
|
220
|
+
class ActivitySet
|
221
|
+
include Enumerable
|
222
|
+
|
223
|
+
# @!visibility private
|
224
|
+
def initialize(activities = [])
|
225
|
+
@activities = activities
|
226
|
+
end
|
227
|
+
|
228
|
+
# @!visibility private
|
229
|
+
# Implement each for Enumerable
|
230
|
+
def each(&block)
|
231
|
+
@activities.each(&block)
|
232
|
+
end
|
233
|
+
|
234
|
+
# @return [Array<Activity>] all activities
|
235
|
+
def to_a
|
236
|
+
@activities
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [Array<Activity>] all activities of type {Activity::GAME}
|
240
|
+
def games
|
241
|
+
@activities.select { |act| act.type == Activity::GAME }
|
242
|
+
end
|
243
|
+
|
244
|
+
# @return [Array<Activity>] all activities of type {Activity::STREAMING}
|
245
|
+
def streaming
|
246
|
+
@activities.select { |act| act.type == Activity::STREAMING }
|
247
|
+
end
|
248
|
+
|
249
|
+
# @return [Array<Activity>] all activities of type {Activity::LISTENING}
|
250
|
+
def listening
|
251
|
+
@activities.select { |act| act.type == Activity::LISTENING }
|
252
|
+
end
|
253
|
+
|
254
|
+
# @return [Array<Activity>] all activities of type {Activity::WATCHING}
|
255
|
+
def watching
|
256
|
+
@activities.select { |act| act.type == Activity::WATCHING }
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [Array<Activity>] all activities of type {Activity::CUSTOM}
|
260
|
+
def custom_status
|
261
|
+
@activities.select { |act| act.type == Activity::CUSTOM }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# OAuth Application information
|
5
|
+
class Application
|
6
|
+
include IDObject
|
7
|
+
|
8
|
+
# @return [String] the application name
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# @return [String] the application description
|
12
|
+
attr_reader :description
|
13
|
+
|
14
|
+
# @return [Array<String>] the application's origins permitted to use RPC
|
15
|
+
attr_reader :rpc_origins
|
16
|
+
|
17
|
+
# @return [Integer]
|
18
|
+
attr_reader :flags
|
19
|
+
|
20
|
+
# Gets the user object of the owner. May be limited to username, discriminator,
|
21
|
+
# ID, and avatar if the bot cannot reach the owner.
|
22
|
+
# @return [User] the user object of the owner
|
23
|
+
attr_reader :owner
|
24
|
+
|
25
|
+
def initialize(data, bot)
|
26
|
+
@bot = bot
|
27
|
+
|
28
|
+
@name = data['name']
|
29
|
+
@id = data['id'].to_i
|
30
|
+
@description = data['description']
|
31
|
+
@icon_id = data['icon']
|
32
|
+
@rpc_origins = data['rpc_origins']
|
33
|
+
@flags = data['flags']
|
34
|
+
@owner = @bot.ensure_user(data['owner'])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Utility function to get a application's icon URL.
|
38
|
+
# @return [String, nil] the URL of the icon image (nil if no image is set).
|
39
|
+
def icon_url
|
40
|
+
return nil if @icon_id.nil?
|
41
|
+
|
42
|
+
API.app_icon_url(@id, @icon_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
# The inspect method is overwritten to give more useful output
|
46
|
+
def inspect
|
47
|
+
"<Application name=#{@name} id=#{@id}>"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# An attachment to a message
|
5
|
+
class Attachment
|
6
|
+
include IDObject
|
7
|
+
|
8
|
+
# @return [Message] the message this attachment belongs to.
|
9
|
+
attr_reader :message
|
10
|
+
|
11
|
+
# @return [String] the CDN URL this attachment can be downloaded at.
|
12
|
+
attr_reader :url
|
13
|
+
|
14
|
+
# @return [String] the attachment's proxy URL - I'm not sure what exactly this does, but I think it has something to
|
15
|
+
# do with CDNs.
|
16
|
+
attr_reader :proxy_url
|
17
|
+
|
18
|
+
# @return [String] the attachment's filename.
|
19
|
+
attr_reader :filename
|
20
|
+
|
21
|
+
# @return [Integer] the attachment's file size in bytes.
|
22
|
+
attr_reader :size
|
23
|
+
|
24
|
+
# @return [Integer, nil] the width of an image file, in pixels, or `nil` if the file is not an image.
|
25
|
+
attr_reader :width
|
26
|
+
|
27
|
+
# @return [Integer, nil] the height of an image file, in pixels, or `nil` if the file is not an image.
|
28
|
+
attr_reader :height
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def initialize(data, message, bot)
|
32
|
+
@bot = bot
|
33
|
+
@message = message
|
34
|
+
|
35
|
+
@id = data['id'].to_i
|
36
|
+
@url = data['url']
|
37
|
+
@proxy_url = data['proxy_url']
|
38
|
+
@filename = data['filename']
|
39
|
+
|
40
|
+
@size = data['size']
|
41
|
+
|
42
|
+
@width = data['width']
|
43
|
+
@height = data['height']
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [true, false] whether this file is an image file.
|
47
|
+
def image?
|
48
|
+
!(@width.nil? || @height.nil?)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [true, false] whether this file is tagged as a spoiler.
|
52
|
+
def spoiler?
|
53
|
+
@filename.start_with? 'SPOILER_'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# A server's audit logs
|
5
|
+
class AuditLogs
|
6
|
+
# The numbers associated with the type of action.
|
7
|
+
ACTIONS = {
|
8
|
+
1 => :server_update,
|
9
|
+
10 => :channel_create,
|
10
|
+
11 => :channel_update,
|
11
|
+
12 => :channel_delete,
|
12
|
+
13 => :channel_overwrite_create,
|
13
|
+
14 => :channel_overwrite_update,
|
14
|
+
15 => :channel_overwrite_delete,
|
15
|
+
20 => :member_kick,
|
16
|
+
21 => :member_prune,
|
17
|
+
22 => :member_ban_add,
|
18
|
+
23 => :member_ban_remove,
|
19
|
+
24 => :member_update,
|
20
|
+
25 => :member_role_update,
|
21
|
+
26 => :member_move,
|
22
|
+
27 => :member_disconnect,
|
23
|
+
28 => :bot_add,
|
24
|
+
30 => :role_create,
|
25
|
+
31 => :role_update,
|
26
|
+
32 => :role_delete,
|
27
|
+
40 => :invite_create,
|
28
|
+
41 => :invite_update,
|
29
|
+
42 => :invite_delete,
|
30
|
+
50 => :webhook_create,
|
31
|
+
51 => :webhook_update,
|
32
|
+
52 => :webhook_delete,
|
33
|
+
60 => :emoji_create,
|
34
|
+
61 => :emoji_update,
|
35
|
+
62 => :emoji_delete,
|
36
|
+
# 70
|
37
|
+
# 71
|
38
|
+
72 => :message_delete,
|
39
|
+
73 => :message_bulk_delete,
|
40
|
+
74 => :message_pin,
|
41
|
+
75 => :message_unpin,
|
42
|
+
80 => :integration_create,
|
43
|
+
81 => :integration_update,
|
44
|
+
82 => :integration_delete
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
CREATE_ACTIONS = %i[
|
49
|
+
channel_create channel_overwrite_create member_ban_add role_create
|
50
|
+
invite_create webhook_create emoji_create integration_create
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
DELETE_ACTIONS = %i[
|
55
|
+
channel_delete channel_overwrite_delete member_kick member_prune
|
56
|
+
member_ban_remove role_delete invite_delete webhook_delete
|
57
|
+
emoji_delete message_delete message_bulk_delete integration_delete
|
58
|
+
].freeze
|
59
|
+
|
60
|
+
# @!visibility private
|
61
|
+
UPDATE_ACTIONS = %i[
|
62
|
+
server_update channel_update channel_overwrite_update member_update
|
63
|
+
member_role_update role_update invite_update webhook_update
|
64
|
+
emoji_update integration_update
|
65
|
+
].freeze
|
66
|
+
|
67
|
+
# @return [Hash<String => User>] the users included in the audit logs.
|
68
|
+
attr_reader :users
|
69
|
+
|
70
|
+
# @return [Hash<String => Webhook>] the webhooks included in the audit logs.
|
71
|
+
attr_reader :webhooks
|
72
|
+
|
73
|
+
# @return [Array<Entry>] the entries listed in the audit logs.
|
74
|
+
attr_reader :entries
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def initialize(server, bot, data)
|
78
|
+
@bot = bot
|
79
|
+
@server = server
|
80
|
+
@users = {}
|
81
|
+
@webhooks = {}
|
82
|
+
@entries = data['audit_log_entries'].map { |entry| Entry.new(self, @server, @bot, entry) }
|
83
|
+
|
84
|
+
process_users(data['users'])
|
85
|
+
process_webhooks(data['webhooks'])
|
86
|
+
end
|
87
|
+
|
88
|
+
# An entry in a server's audit logs.
|
89
|
+
class Entry
|
90
|
+
include IDObject
|
91
|
+
|
92
|
+
# @return [Symbol] the action that was performed.
|
93
|
+
attr_reader :action
|
94
|
+
|
95
|
+
# @return [Symbol] the type action that was performed. (:create, :delete, :update, :unknown)
|
96
|
+
attr_reader :action_type
|
97
|
+
|
98
|
+
# @return [Symbol] the type of target being performed on. (:server, :channel, :user, :role, :invite, :webhook, :emoji, :unknown)
|
99
|
+
attr_reader :target_type
|
100
|
+
|
101
|
+
# @return [Integer, nil] the amount of messages deleted. Only present if the action is `:message_delete`.
|
102
|
+
attr_reader :count
|
103
|
+
alias_method :amount, :count
|
104
|
+
|
105
|
+
# @return [Integer, nil] the amount of days the members were inactive for. Only present if the action is `:member_prune`.
|
106
|
+
attr_reader :days
|
107
|
+
|
108
|
+
# @return [Integer, nil] the amount of members removed. Only present if the action is `:member_prune`.
|
109
|
+
attr_reader :members_removed
|
110
|
+
|
111
|
+
# @return [String, nil] the reason for this action occurring.
|
112
|
+
attr_reader :reason
|
113
|
+
|
114
|
+
# @return [Hash<String => Change>, RoleChange, nil] the changes from this log, listing the key as the key changed. Will be a RoleChange object if the action is `:member_role_update`. Will be nil if the action is either `:message_delete` or `:member_prune`.
|
115
|
+
attr_reader :changes
|
116
|
+
|
117
|
+
# @!visibility private
|
118
|
+
def initialize(logs, server, bot, data)
|
119
|
+
@bot = bot
|
120
|
+
@id = data['id'].resolve_id
|
121
|
+
@logs = logs
|
122
|
+
@server = server
|
123
|
+
@data = data
|
124
|
+
@action = ACTIONS[data['action_type']]
|
125
|
+
@reason = data['reason']
|
126
|
+
@action_type = AuditLogs.action_type_for(data['action_type'])
|
127
|
+
@target_type = AuditLogs.target_type_for(data['action_type'])
|
128
|
+
|
129
|
+
# Sets the 'changes' variable to a empty hash if there are no special actions.
|
130
|
+
@changes = {} unless @action == :message_delete || @action == :member_prune || @action == :member_role_update
|
131
|
+
|
132
|
+
# Sets the 'changes' variable to a RoleChange class if there's a role update.
|
133
|
+
@changes = RoleChange.new(data['changes'][0], @server) if @action == :member_role_update
|
134
|
+
|
135
|
+
process_changes(data['changes']) unless @action == :member_role_update
|
136
|
+
return unless data.include?('options')
|
137
|
+
|
138
|
+
# Checks and sets variables for special action options.
|
139
|
+
@count = data['options']['count'].to_i unless data['options']['count'].nil?
|
140
|
+
@channel_id = data['options']['channel'].to_i unless data['options']['channel'].nil?
|
141
|
+
@days = data['options']['delete_member_days'].to_i unless data['options']['delete_member_days'].nil?
|
142
|
+
@members_removed = data['options']['members_removed'].to_i unless data['options']['members_removed'].nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [Server, Channel, Member, User, Role, Invite, Webhook, Emoji, nil] the target being performed on.
|
146
|
+
def target
|
147
|
+
@target ||= process_target(@data['target_id'], @target_type)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [Member, User] the user that authored this action. Can be a User object if the user no longer exists in the server.
|
151
|
+
def user
|
152
|
+
@user ||= @server.member(@data['user_id'].to_i) || @bot.user(@data['user_id'].to_i) || @logs.user(@data['user_id'].to_i)
|
153
|
+
end
|
154
|
+
alias_method :author, :user
|
155
|
+
|
156
|
+
# @return [Channel, nil] the amount of messages deleted. Won't be nil if the action is `:message_delete`.
|
157
|
+
def channel
|
158
|
+
return nil unless @channel_id
|
159
|
+
|
160
|
+
@channel ||= @bot.channel(@channel_id, @server, bot, self)
|
161
|
+
end
|
162
|
+
|
163
|
+
# @!visibility private
|
164
|
+
def process_target(id, type)
|
165
|
+
id = id.resolve_id unless id.nil?
|
166
|
+
case type
|
167
|
+
when :server then @server # Since it won't be anything else
|
168
|
+
when :channel then @bot.channel(id, @server)
|
169
|
+
when :user, :message then @server.member(id) || @bot.user(id) || @logs.user(id)
|
170
|
+
when :role then @server.role(id)
|
171
|
+
when :invite then @bot.invite(@data['changes'].find { |change| change['key'] == 'code' }.values.delete_if { |v| v == 'code' }.first)
|
172
|
+
when :webhook then @server.webhooks.find { |webhook| webhook.id == id } || @logs.webhook(id)
|
173
|
+
when :emoji then @server.emoji[id]
|
174
|
+
when :integration then @server.integrations.find { |integration| integration.id == id }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# The inspect method is overwritten to give more useful output
|
179
|
+
def inspect
|
180
|
+
"<AuditLogs::Entry id=#{@id} action=#{@action} reason=#{@reason} action_type=#{@action_type} target_type=#{@target_type} count=#{@count} days=#{@days} members_removed=#{@members_removed}>"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Process action changes
|
184
|
+
# @note For internal use only
|
185
|
+
# @!visibility private
|
186
|
+
def process_changes(changes)
|
187
|
+
return unless changes
|
188
|
+
|
189
|
+
changes.each do |element|
|
190
|
+
change = Change.new(element, @server, @bot, self)
|
191
|
+
@changes[change.key] = change
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# A change in a audit log entry.
|
197
|
+
class Change
|
198
|
+
# @return [String] the key that was changed.
|
199
|
+
# @note You should check with the Discord API Documentation on what key gives out what value.
|
200
|
+
attr_reader :key
|
201
|
+
|
202
|
+
# @return [String, Integer, true, false, Permissions, Overwrite, nil] the value that was changed from.
|
203
|
+
attr_reader :old
|
204
|
+
alias_method :old_value, :old
|
205
|
+
|
206
|
+
# @return [String, Integer, true, false, Permissions, Overwrite, nil] the value that was changed to.
|
207
|
+
attr_reader :new
|
208
|
+
alias_method :new_value, :new
|
209
|
+
|
210
|
+
# @!visibility private
|
211
|
+
def initialize(data, server, bot, logs)
|
212
|
+
@key = data['key']
|
213
|
+
@old = data['old_value']
|
214
|
+
@new = data['new_value']
|
215
|
+
@server = server
|
216
|
+
@bot = bot
|
217
|
+
@logs = logs
|
218
|
+
|
219
|
+
@old = Permissions.new(@old) if @old && @key == 'permissions'
|
220
|
+
@new = Permissions.new(@new) if @new && @key == 'permissions'
|
221
|
+
|
222
|
+
@old = @old.map { |o| Overwrite.new(o['id'], type: o['type'].to_sym, allow: o['allow'], deny: o['deny']) } if @old && @key == 'permission_overwrites'
|
223
|
+
@new = @new.map { |o| Overwrite.new(o['id'], type: o['type'].to_sym, allow: o['allow'], deny: o['deny']) } if @new && @key == 'permission_overwrites'
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [Channel, nil] the channel that was previously used in the server widget. Only present if the key for this change is `widget_channel_id`.
|
227
|
+
def old_widget_channel
|
228
|
+
@bot.channel(@old, @server) if @old && @key == 'widget_channel_id'
|
229
|
+
end
|
230
|
+
|
231
|
+
# @return [Channel, nil] the channel that is used in the server widget prior to this change. Only present if the key for this change is `widget_channel_id`.
|
232
|
+
def new_widget_channel
|
233
|
+
@bot.channel(@new, @server) if @new && @key == 'widget_channel_id'
|
234
|
+
end
|
235
|
+
|
236
|
+
# @return [Channel, nil] the channel that was previously used in the server as an AFK channel. Only present if the key for this change is `afk_channel_id`.
|
237
|
+
def old_afk_channel
|
238
|
+
@bot.channel(@old, @server) if @old && @key == 'afk_channel_id'
|
239
|
+
end
|
240
|
+
|
241
|
+
# @return [Channel, nil] the channel that is used in the server as an AFK channel prior to this change. Only present if the key for this change is `afk_channel_id`.
|
242
|
+
def new_afk_channel
|
243
|
+
@bot.channel(@new, @server) if @new && @key == 'afk_channel_id'
|
244
|
+
end
|
245
|
+
|
246
|
+
# @return [Member, User, nil] the member that used to be the owner of the server. Only present if the for key for this change is `owner_id`.
|
247
|
+
def old_owner
|
248
|
+
@server.member(@old) || @bot.user(@old) || @logs.user(@old) if @old && @key == 'owner_id'
|
249
|
+
end
|
250
|
+
|
251
|
+
# @return [Member, User, nil] the member that is now the owner of the server prior to this change. Only present if the key for this change is `owner_id`.
|
252
|
+
def new_owner
|
253
|
+
@server.member(@new) || @bot.user(@new) || @logs.user(@new) if @new && @key == 'owner_id'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# A change that includes roles.
|
258
|
+
class RoleChange
|
259
|
+
# @return [Symbol] what type of change this is: (:add, :remove)
|
260
|
+
attr_reader :type
|
261
|
+
|
262
|
+
# @!visibility private
|
263
|
+
def initialize(data, server)
|
264
|
+
@type = data['key'].delete('$').to_sym
|
265
|
+
@role_id = data['new_value'][0]['id'].to_i
|
266
|
+
@server = server
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [Role] the role being used.
|
270
|
+
def role
|
271
|
+
@role ||= @server.role(@role_id)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# @return [Entry] the latest entry in the audit logs.
|
276
|
+
def latest
|
277
|
+
@entries.first
|
278
|
+
end
|
279
|
+
alias_method :first, :latest
|
280
|
+
|
281
|
+
# Gets a user in the audit logs data based on user ID
|
282
|
+
# @note This only uses data given by the audit logs request
|
283
|
+
# @param id [String, Integer] The user ID to look for
|
284
|
+
def user(id)
|
285
|
+
@users[id.resolve_id]
|
286
|
+
end
|
287
|
+
|
288
|
+
# Gets a webhook in the audit logs data based on webhook ID
|
289
|
+
# @note This only uses data given by the audit logs request
|
290
|
+
# @param id [String, Integer] The webhook ID to look for
|
291
|
+
def webhook(id)
|
292
|
+
@webhooks[id.resolve_id]
|
293
|
+
end
|
294
|
+
|
295
|
+
# Process user objects given by the request
|
296
|
+
# @note For internal use only
|
297
|
+
# @!visibility private
|
298
|
+
def process_users(users)
|
299
|
+
users.each do |element|
|
300
|
+
user = User.new(element, @bot)
|
301
|
+
@users[user.id] = user
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Process webhook objects given by the request
|
306
|
+
# @note For internal use only
|
307
|
+
# @!visibility private
|
308
|
+
def process_webhooks(webhooks)
|
309
|
+
webhooks.each do |element|
|
310
|
+
webhook = Webhook.new(element, @bot)
|
311
|
+
@webhooks[webhook.id] = webhook
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Find the type of target by it's action number
|
316
|
+
# @note For internal use only
|
317
|
+
# @!visibility private
|
318
|
+
def self.target_type_for(action)
|
319
|
+
case action
|
320
|
+
when 1..9 then :server
|
321
|
+
when 10..19 then :channel
|
322
|
+
when 20..29 then :user
|
323
|
+
when 30..39 then :role
|
324
|
+
when 40..49 then :invite
|
325
|
+
when 50..59 then :webhook
|
326
|
+
when 60..69 then :emoji
|
327
|
+
when 70..79 then :message
|
328
|
+
when 80..89 then :integration
|
329
|
+
else :unknown
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Find the type of action by its action number
|
334
|
+
# @note For internal use only
|
335
|
+
# @!visibility private
|
336
|
+
def self.action_type_for(action)
|
337
|
+
action = ACTIONS[action]
|
338
|
+
return :create if CREATE_ACTIONS.include?(action)
|
339
|
+
return :delete if DELETE_ACTIONS.include?(action)
|
340
|
+
return :update if UPDATE_ACTIONS.include?(action)
|
341
|
+
|
342
|
+
:unknown
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|