mij-discord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE +21 -0
  6. data/README.md +35 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +7 -0
  9. data/bin/setup +6 -0
  10. data/lib/mij-discord.rb +56 -0
  11. data/lib/mij-discord/bot.rb +579 -0
  12. data/lib/mij-discord/cache.rb +298 -0
  13. data/lib/mij-discord/core/api.rb +228 -0
  14. data/lib/mij-discord/core/api/channel.rb +416 -0
  15. data/lib/mij-discord/core/api/invite.rb +43 -0
  16. data/lib/mij-discord/core/api/server.rb +465 -0
  17. data/lib/mij-discord/core/api/user.rb +144 -0
  18. data/lib/mij-discord/core/errors.rb +106 -0
  19. data/lib/mij-discord/core/gateway.rb +505 -0
  20. data/lib/mij-discord/data.rb +65 -0
  21. data/lib/mij-discord/data/application.rb +38 -0
  22. data/lib/mij-discord/data/channel.rb +404 -0
  23. data/lib/mij-discord/data/embed.rb +115 -0
  24. data/lib/mij-discord/data/emoji.rb +62 -0
  25. data/lib/mij-discord/data/invite.rb +87 -0
  26. data/lib/mij-discord/data/member.rb +174 -0
  27. data/lib/mij-discord/data/message.rb +206 -0
  28. data/lib/mij-discord/data/permissions.rb +121 -0
  29. data/lib/mij-discord/data/role.rb +99 -0
  30. data/lib/mij-discord/data/server.rb +359 -0
  31. data/lib/mij-discord/data/user.rb +173 -0
  32. data/lib/mij-discord/data/voice.rb +68 -0
  33. data/lib/mij-discord/events.rb +133 -0
  34. data/lib/mij-discord/events/basic.rb +80 -0
  35. data/lib/mij-discord/events/channel.rb +50 -0
  36. data/lib/mij-discord/events/member.rb +66 -0
  37. data/lib/mij-discord/events/message.rb +150 -0
  38. data/lib/mij-discord/events/server.rb +102 -0
  39. data/lib/mij-discord/logger.rb +20 -0
  40. data/lib/mij-discord/version.rb +5 -0
  41. data/mij-discord.gemspec +31 -0
  42. metadata +154 -0
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Data
4
+ class Permissions
5
+ FLAGS = {
6
+ # (1 << Bit) => Permission # Value
7
+ (1 << 0) => :create_instant_invite, # 1
8
+ (1 << 1) => :kick_members, # 2
9
+ (1 << 2) => :ban_members, # 4
10
+ (1 << 3) => :administrator, # 8
11
+ (1 << 4) => :manage_channels, # 16
12
+ (1 << 5) => :manage_server, # 32
13
+ (1 << 6) => :add_reactions, # 64
14
+ (1 << 7) => :view_audit_log, # 128
15
+ # 1 << 8 # 256
16
+ # 1 << 9 # 512
17
+ (1 << 10) => :read_messages, # 1024
18
+ (1 << 11) => :send_messages, # 2048
19
+ (1 << 12) => :send_tts_messages, # 4096
20
+ (1 << 13) => :manage_messages, # 8192
21
+ (1 << 14) => :embed_links, # 16384
22
+ (1 << 15) => :attach_files, # 32768
23
+ (1 << 16) => :read_message_history, # 65536
24
+ (1 << 17) => :mention_everyone, # 131072
25
+ (1 << 18) => :use_external_emoji, # 262144
26
+ # 1 << 19 # 524288
27
+ (1 << 20) => :connect, # 1048576
28
+ (1 << 21) => :speak, # 2097152
29
+ (1 << 22) => :mute_members, # 4194304
30
+ (1 << 23) => :deafen_members, # 8388608
31
+ (1 << 24) => :move_members, # 16777216
32
+ (1 << 25) => :use_voice_activity, # 33554432
33
+ (1 << 26) => :change_nickname, # 67108864
34
+ (1 << 27) => :manage_nicknames, # 134217728
35
+ (1 << 28) => :manage_roles, # 268435456
36
+ (1 << 29) => :manage_webhooks, # 536870912
37
+ (1 << 30) => :manage_emojis, # 1073741824
38
+ }.freeze
39
+
40
+ def self.bits(list)
41
+ FLAGS.map {|bit, name| list.include?(name) ? bit : 0 }.reduce(&:|)
42
+ end
43
+
44
+ FLAGS.each do |bit, name|
45
+ attr_reader name
46
+ alias_method :"#{name}?", name
47
+
48
+ define_method(:"can_#{name}=") do |flag|
49
+ set_bits(flag ? (@bits | bit) : (@bits & ~bit))
50
+ end
51
+ end
52
+
53
+ alias_method :can_administrate=, :can_administrator=
54
+ alias_method :administrate, :administrator
55
+ alias_method :administrate?, :administrator?
56
+
57
+ attr_reader :bits
58
+ alias_method :to_i, :bits
59
+
60
+ def initialize(bits = 0)
61
+ set_bits(bits)
62
+ end
63
+
64
+ def set_bits(bits)
65
+ @bits = bits.is_a?(Array) ? Permissions.bits(bits) : bits
66
+
67
+ FLAGS.each do |bit, name|
68
+ flag = !(@bits & bit).zero?
69
+ instance_variable_set "@#{name}", flag
70
+ end
71
+ end
72
+
73
+ alias_method :bits=, :set_bits
74
+ end
75
+
76
+ class Overwrite
77
+ include IDObject
78
+
79
+ attr_accessor :type
80
+
81
+ attr_accessor :allow
82
+
83
+ attr_accessor :deny
84
+
85
+ def initialize(object, type: nil, allow: 0, deny: 0)
86
+ case type
87
+ when nil, :member, :role
88
+ # Do nothing
89
+ else
90
+ raise ArgumentError, 'Overwrite type must be :member or :role'
91
+ end
92
+
93
+ @id = object.to_id
94
+
95
+ @type = case object
96
+ when User, Member, Recipient, Profile
97
+ :member
98
+ when Role
99
+ :role
100
+ else
101
+ type
102
+ end
103
+
104
+ @allow = allow.is_a?(Permissions) ? allow : Permissions.new(allow)
105
+ @deny = deny.is_a?(Permissions) ? deny : Permissions.new(deny)
106
+ end
107
+
108
+ def self.from_hash(data)
109
+ Overwrite.new(
110
+ data['id'].to_i,
111
+ type: data['type'].to_sym,
112
+ allow: Permissions.new(data['allow']),
113
+ deny: Permissions.new(data['deny'])
114
+ )
115
+ end
116
+
117
+ def to_hash
118
+ { id: @id, type: @type, allow: @allow.bits, deny: @deny.bits }
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Data
4
+ class Role
5
+ include IDObject
6
+
7
+ attr_reader :permissions
8
+
9
+ attr_reader :name
10
+
11
+ attr_reader :hoist
12
+
13
+ attr_reader :managed
14
+ alias_method :managed?, :managed
15
+
16
+ attr_reader :mentionable
17
+ alias_method :mentionable?, :mentionable
18
+
19
+ attr_reader :color
20
+
21
+ attr_reader :position
22
+
23
+ def initialize(data, server, bot)
24
+ @bot, @server = bot, server
25
+
26
+ @permissions = Permissions.new
27
+ @color = ColorRGB.new
28
+
29
+ @id = data['id'].to_i
30
+ update_data(data)
31
+ end
32
+
33
+ def update_data(data)
34
+ @name = data.fetch('name', @name)
35
+ @position = data.fetch('position', @position)
36
+ @hoist = data.fetch('hoist', @hoist)
37
+ @mentionable = data.fetch('mentionable', @mentionable)
38
+ @managed = data.fetch('managed', @managed)
39
+
40
+ @color.value = data.fetch('color', @color.value)
41
+
42
+ if (bits = data['permissions'])
43
+ @permissions.bits = bits.to_i
44
+ end
45
+ end
46
+
47
+ def mention
48
+ "<@&#{@id}>"
49
+ end
50
+
51
+ alias_method :to_s, :mention
52
+
53
+ def members
54
+ @server.members.select {|x| x.role?(self) }
55
+ end
56
+
57
+ alias_method :users, :members
58
+
59
+ def set_name(name)
60
+ set_options(name: name)
61
+ end
62
+
63
+ alias_method :name=, :set_name
64
+
65
+ def set_hoist(flag)
66
+ set_options(hoist: flag)
67
+ end
68
+
69
+ alias_method :hoist=, :set_hoist
70
+
71
+ def set_mentionable(flag)
72
+ set_options(mentionable: flag)
73
+ end
74
+
75
+ alias_method :mentionable=, :set_mentionable
76
+
77
+ def set_color(color)
78
+ set_options(color: color)
79
+ end
80
+
81
+ alias_method :color=, :set_color
82
+
83
+ def delete(reason = nil)
84
+ MijDiscord::Core::API::Server.delete_role(@bot.token, @server.id, @id, reason)
85
+ end
86
+
87
+ def set_options(name: nil, color: nil, hoist: nil, mentionable: nil, permissions: nil)
88
+ response = MijDiscord::Core::API::Server.update_role(@bot.token, @server.id, @id,
89
+ name, color&.to_i, hoist, mentionable, permissions&.to_i)
90
+ @server.cache.put_role(JSON.parse(response), update: true)
91
+ end
92
+
93
+ def inspect
94
+ %(<Role id=#{@id} name=#{@name} server=#{@server.inspect}>)
95
+ end
96
+
97
+ private
98
+ end
99
+ end
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Data
4
+ class Server
5
+ VERIFICATION_LEVEL = {
6
+ 0 => :none,
7
+ 1 => :low,
8
+ 2 => :medium,
9
+ 3 => :high,
10
+ 4 => :very_high,
11
+ }.freeze
12
+
13
+ CONTENT_FILTER_LEVEL = {
14
+ 0 => :none,
15
+ 1 => :no_roles,
16
+ 2 => :all,
17
+ }.freeze
18
+
19
+ DEFAULT_NOTIFICATIONS = {
20
+ 0 => :all,
21
+ 1 => :mentions,
22
+ }.freeze
23
+
24
+ include IDObject
25
+
26
+ attr_reader :bot
27
+
28
+ attr_reader :name
29
+
30
+ attr_reader :icon_id
31
+
32
+ attr_reader :owner
33
+
34
+ attr_reader :large
35
+ alias_method :large?, :large
36
+
37
+ attr_reader :features
38
+
39
+ attr_reader :embed_enabled
40
+ alias_method :embed_enabled?, :embed_enabled
41
+ alias_method :has_embed?, :embed_enabled
42
+
43
+ # attr_reader :member_count
44
+
45
+ attr_reader :verification_level
46
+
47
+ attr_reader :content_filter_level
48
+ alias_method :content_filter, :content_filter_level
49
+
50
+ attr_reader :default_notifications
51
+
52
+ attr_reader :afk_timeout
53
+
54
+ attr_reader :afk_channel
55
+
56
+ attr_reader :cache
57
+
58
+ def initialize(data, bot, invalid = false)
59
+ @bot = bot
60
+
61
+ @id = data['id'].to_i
62
+ @large = data['large']
63
+ @members_init = data['member_count']
64
+ @members_chunked = 0
65
+
66
+ @cache = MijDiscord::Cache::ServerCache.new(self, @bot)
67
+
68
+ data['channels']&.each {|ch| @cache.put_channel(ch) }
69
+
70
+ data['roles']&.each {|ro| @cache.put_role(ro) }
71
+
72
+ data['members']&.each {|mb| @cache.put_member(mb) }
73
+
74
+ data['presences']&.each do |pr|
75
+ next unless pr['user']
76
+
77
+ user_id = pr['user']['id'].to_i
78
+ user = @cache.get_member(user_id, local: true)
79
+ user.update_presence(pr) if user
80
+ end
81
+
82
+ @voice_states = {}
83
+ data['voice_states']&.each {|vs| update_voice_state(vs) }
84
+
85
+ update_emojis(data)
86
+ update_data(data)
87
+
88
+ @owner = member(data['owner_id'])
89
+ end
90
+
91
+ def update_data(data)
92
+ @name = data.fetch('name', @name)
93
+ @region_id = data.fetch('region', @region_id)
94
+ @icon_id = data.fetch('icon', @icon_id)
95
+ @afk_timeout = data.fetch('afk_timeout', @afk_timeout)
96
+ @embed_enabled = data.fetch('embed_enabled', @embed_enabled)
97
+ @verification_level = VERIFICATION_LEVEL.fetch(data['verification_level'], @verification_level)
98
+ @content_filter_level = CONTENT_FILTER_LEVEL.fetch(data['explicit_content_filter'], @content_filter_level)
99
+ @default_notifications = DEFAULT_NOTIFICATIONS.fetch(data['default_message_notifications'], @default_notifications)
100
+
101
+ begin
102
+ if data.has_key?('afk_channel_id')
103
+ id = data['afk_channel_id'].to_i
104
+ @afk_channel = @bot.channel(id, self)
105
+ end
106
+ rescue MijDiscord::Errors::NoPermission
107
+ @afk_channel = nil
108
+ end
109
+ end
110
+
111
+ def update_emojis(data)
112
+ @emojis = {}
113
+ data['emojis'].each do |em|
114
+ emoji = MijDiscord::Data::Emoji.new(em, @bot, self)
115
+ @emojis[emoji.id] = emoji
116
+ end
117
+ end
118
+
119
+ def update_voice_state(data)
120
+ user = @cache.get_member(data['user_id'])
121
+
122
+ if (chan_id = data['channel_id']&.to_i)
123
+ state = (@voice_states[user.id] ||= VoiceState.new(user))
124
+ channel = @cache.get_channel(chan_id)
125
+
126
+ state.update_data(channel, data) if channel
127
+ state
128
+ else
129
+ state = @voice_states.delete(user.id)
130
+ state ||= VoiceState.new(user)
131
+
132
+ state.update_data(nil, data)
133
+ state
134
+ end
135
+ end
136
+
137
+ def update_members_chunk(data)
138
+ return if @members_chunked.nil?
139
+
140
+ @members_chunked += data.length
141
+ data.each {|mb| @cache.put_member(mb) }
142
+
143
+ @members_chunked = nil if @members_chunked == @members_init
144
+ end
145
+
146
+ def channels
147
+ @cache.list_channels
148
+ end
149
+
150
+ def channel(id)
151
+ @cache.get_channel(id)
152
+ end
153
+
154
+ def roles
155
+ @cache.list_roles
156
+ end
157
+
158
+ def role(id)
159
+ @cache.get_role(id)
160
+ end
161
+
162
+ def members
163
+ unless @members_chunked.nil?
164
+ @bot.gateway.send_request_members(@id, '', 0)
165
+ sleep(0.05) while @members_chunked
166
+ end
167
+
168
+ @cache.list_members
169
+ end
170
+
171
+ def member(id)
172
+ @cache.get_member(id)
173
+ end
174
+
175
+ def emojis
176
+ @emojis.values
177
+ end
178
+
179
+ def emoji(id)
180
+ @emojis[id&.to_id]
181
+ end
182
+
183
+ def everyone_role
184
+ role(@id)
185
+ end
186
+
187
+ def voice_states
188
+ @voice_states.values
189
+ end
190
+
191
+ def voice_state(id)
192
+ @voice_states[id&.to_id]
193
+ end
194
+
195
+ def default_channel
196
+ text_channels.sort_by {|ch| [ch.position, ch.id] }.find do |ch|
197
+ if (overwrite = ch.permission_overwrites[id])
198
+ overwrite.allow.read_messages? || !overwrite.deny.read_messages?
199
+ else
200
+ everyone_role.permissions.read_messages
201
+ end
202
+ end
203
+ end
204
+
205
+ alias_method :general_channel, :default_channel
206
+
207
+ def online_members(idle: true, bots: false)
208
+ members.select! do |m|
209
+ ((idle ? m.idle? : false) || m.online?) && (bots ? true : !m.bot_account?)
210
+ end
211
+ end
212
+
213
+ def invites
214
+ response = MijDiscord::Core::API::Server.invites(@bot.token, @id)
215
+ JSON.parse(response).map {|x| Invite.new(x, @bot) }
216
+ end
217
+
218
+ def prune_count(days)
219
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
220
+
221
+ response = MijDiscord::Core::API::Server.prune_count(@bot.token, @id, days)
222
+ JSON.parse(response)['pruned']
223
+ end
224
+
225
+ def prune(days, reason = nil)
226
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
227
+
228
+ response = MijDiscord::Core::API::Server.begin_prune(@bot.token, @id, days, reason)
229
+ JSON.parse(response)['pruned']
230
+ end
231
+
232
+ def text_channels
233
+ channels.select!(&:text?)
234
+ end
235
+
236
+ def voice_channels
237
+ channels.select!(&:voice?)
238
+ end
239
+
240
+ def create_channel(name, reason = nil, voice: false, bitrate: nil, user_limit: nil, permissions: [], nsfw: false)
241
+ permissions = permissions.map {|x| x.is_a(Overwrite) ? x.to_hash : x }
242
+ response = MijDiscord::Core::API::Server.create_channel(@bot.token, @id,
243
+ name, voice ? 2 : 0, bitrate, user_limit, permissions, nsfw, reason)
244
+ @cache.put_channel(JSON.parse(response))
245
+ end
246
+
247
+ def create_role(name, reason = nil, color: 0, hoist: false, mentionable: false, permissions: 104_324_161)
248
+ response = MijDiscord::Core::API::Server.create_role(@bot.token, @id,
249
+ name, color.to_i, hoist, mentionable, permissions.to_i, reason)
250
+ @cache.put_role(JSON.parse(response))
251
+ end
252
+
253
+ def bans
254
+ response = MijDiscord::Core::API::Server.bans(@bot.token, @id)
255
+ JSON.parse(response).map {|x| @bot.cache.put_user(x['user']) }
256
+ end
257
+
258
+ def ban(user, message_days = 0, reason = nil)
259
+ MijDiscord::Core::API::Server.ban_user(@bot.token, @id, user.to_id, message_days, reason)
260
+ nil
261
+ end
262
+
263
+ def unban(user, reason = nil)
264
+ MijDiscord::Core::API::Server.unban_user(@bot.token, @id, user.to_id, reason)
265
+ nil
266
+ end
267
+
268
+ def kick(user, reason = nil)
269
+ MijDiscord::Core::API::Server.remove_member(@bot.token, @id, user.to_id, reason)
270
+ nil
271
+ end
272
+
273
+ def leave
274
+ MijDiscord::Core::API::User.leave_server(@bot.token, @id)
275
+ nil
276
+ end
277
+
278
+ def delete
279
+ MijDiscord::Core::API::Server.delete(@bot.token, @id)
280
+ nil
281
+ end
282
+
283
+ def set_owner(user, reason = nil)
284
+ MijDiscord::Core::API::Server.transfer_ownership(@bot.token, @id, user.to_id, reason)
285
+ end
286
+
287
+ alias_method :owner=, :set_owner
288
+
289
+ def set_options(reason = nil, name: nil, region: nil, icon: nil, afk_channel: nil, afk_timeout: nil)
290
+ response = MijDiscord::Core::API::Server.update(@bot.token, @id,
291
+ name, region, icon, afk_channel&.to_id, afk_timeout, reason)
292
+ @bot.cache.put_server(JSON.parse(response), update: true)
293
+ end
294
+
295
+ def set_name(name, reason = nil)
296
+ set_options(reason, name: name)
297
+ end
298
+
299
+ alias_method :name=, :set_name
300
+
301
+ def available_regions
302
+ return @voice_regions if @voice_regions
303
+
304
+ response = MijDiscord::Core::API::Server.regions(@bot.token, @id)
305
+ @voice_regions = JSON.parse(response).map {|x| VoiceRegion.new(x) }
306
+ end
307
+
308
+ def region
309
+ available_regions.find {|x| x.id == @region_id }
310
+ end
311
+
312
+ def set_region(region, reason = nil)
313
+ region = region.to_s
314
+ raise ArgumentError, 'Invalid region' unless available_regions.find {|x| x.id == region }
315
+
316
+ set_options(reason, region: region)
317
+ end
318
+
319
+ alias_method :region=, :set_region
320
+
321
+ def set_icon(icon, reason = nil)
322
+ if icon.respond_to?(:read)
323
+ buffer = Base64.strict_encode64(icon.read)
324
+ icon = "data:image/jpg;base64,#{buffer}"
325
+ set_options(reason, icon: icon)
326
+ else
327
+ set_options(reason, icon: icon.to_s)
328
+ end
329
+ end
330
+
331
+ alias_method :icon=, :set_icon
332
+
333
+ def set_afk_channel(channel, reason = nil)
334
+ set_options(reason, afk_channel: channel.to_id)
335
+ end
336
+
337
+ alias_method :afk_channel=, :set_afk_channel
338
+
339
+ def set_afk_timeout(timeout, reason = nil)
340
+ set_options(reason, afk_timeout: timeout)
341
+ end
342
+
343
+ alias_method :afk_timeout=, :set_afk_timeout
344
+
345
+ def icon_url(format = :webp)
346
+ return nil unless @icon_id
347
+ MijDiscord::Core::API.icon_url(@id, @icon_id, format)
348
+ end
349
+
350
+ def embed_url(style = :shield)
351
+ return nil unless @embed_enabled
352
+ MijDiscord::Core::API.widget_url(@id, style)
353
+ end
354
+
355
+ def inspect
356
+ %(<Server id=#{@id} name="#{@name}" large=#{@large} region=#{@region_id} owner=#{@owner.id}>)
357
+ end
358
+ end
359
+ end