rubycord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rubycord/allowed_mentions.rb +34 -0
  3. data/lib/rubycord/api/application.rb +200 -0
  4. data/lib/rubycord/api/channel.rb +597 -0
  5. data/lib/rubycord/api/interaction.rb +52 -0
  6. data/lib/rubycord/api/invite.rb +42 -0
  7. data/lib/rubycord/api/server.rb +557 -0
  8. data/lib/rubycord/api/user.rb +153 -0
  9. data/lib/rubycord/api/webhook.rb +138 -0
  10. data/lib/rubycord/api.rb +356 -0
  11. data/lib/rubycord/await.rb +49 -0
  12. data/lib/rubycord/bot.rb +1757 -0
  13. data/lib/rubycord/cache.rb +259 -0
  14. data/lib/rubycord/colour_rgb.rb +41 -0
  15. data/lib/rubycord/commands/command_bot.rb +519 -0
  16. data/lib/rubycord/commands/container.rb +110 -0
  17. data/lib/rubycord/commands/events.rb +9 -0
  18. data/lib/rubycord/commands/parser.rb +325 -0
  19. data/lib/rubycord/commands/rate_limiter.rb +142 -0
  20. data/lib/rubycord/container.rb +753 -0
  21. data/lib/rubycord/data/activity.rb +269 -0
  22. data/lib/rubycord/data/application.rb +48 -0
  23. data/lib/rubycord/data/attachment.rb +109 -0
  24. data/lib/rubycord/data/audit_logs.rb +343 -0
  25. data/lib/rubycord/data/channel.rb +996 -0
  26. data/lib/rubycord/data/component.rb +227 -0
  27. data/lib/rubycord/data/embed.rb +249 -0
  28. data/lib/rubycord/data/emoji.rb +80 -0
  29. data/lib/rubycord/data/integration.rb +120 -0
  30. data/lib/rubycord/data/interaction.rb +798 -0
  31. data/lib/rubycord/data/invite.rb +135 -0
  32. data/lib/rubycord/data/member.rb +370 -0
  33. data/lib/rubycord/data/message.rb +412 -0
  34. data/lib/rubycord/data/overwrite.rb +106 -0
  35. data/lib/rubycord/data/profile.rb +89 -0
  36. data/lib/rubycord/data/reaction.rb +31 -0
  37. data/lib/rubycord/data/recipient.rb +32 -0
  38. data/lib/rubycord/data/role.rb +246 -0
  39. data/lib/rubycord/data/server.rb +1002 -0
  40. data/lib/rubycord/data/user.rb +261 -0
  41. data/lib/rubycord/data/voice_region.rb +43 -0
  42. data/lib/rubycord/data/voice_state.rb +39 -0
  43. data/lib/rubycord/data/webhook.rb +232 -0
  44. data/lib/rubycord/data.rb +40 -0
  45. data/lib/rubycord/errors.rb +737 -0
  46. data/lib/rubycord/events/await.rb +46 -0
  47. data/lib/rubycord/events/bans.rb +58 -0
  48. data/lib/rubycord/events/channels.rb +186 -0
  49. data/lib/rubycord/events/generic.rb +126 -0
  50. data/lib/rubycord/events/guilds.rb +191 -0
  51. data/lib/rubycord/events/interactions.rb +480 -0
  52. data/lib/rubycord/events/invites.rb +123 -0
  53. data/lib/rubycord/events/lifetime.rb +29 -0
  54. data/lib/rubycord/events/members.rb +91 -0
  55. data/lib/rubycord/events/message.rb +337 -0
  56. data/lib/rubycord/events/presence.rb +127 -0
  57. data/lib/rubycord/events/raw.rb +45 -0
  58. data/lib/rubycord/events/reactions.rb +156 -0
  59. data/lib/rubycord/events/roles.rb +86 -0
  60. data/lib/rubycord/events/threads.rb +94 -0
  61. data/lib/rubycord/events/typing.rb +70 -0
  62. data/lib/rubycord/events/voice_server_update.rb +45 -0
  63. data/lib/rubycord/events/voice_state_update.rb +103 -0
  64. data/lib/rubycord/events/webhooks.rb +62 -0
  65. data/lib/rubycord/gateway.rb +867 -0
  66. data/lib/rubycord/id_object.rb +37 -0
  67. data/lib/rubycord/light/data.rb +60 -0
  68. data/lib/rubycord/light/integrations.rb +71 -0
  69. data/lib/rubycord/light/light_bot.rb +56 -0
  70. data/lib/rubycord/light.rb +6 -0
  71. data/lib/rubycord/logger.rb +118 -0
  72. data/lib/rubycord/paginator.rb +55 -0
  73. data/lib/rubycord/permissions.rb +251 -0
  74. data/lib/rubycord/version.rb +5 -0
  75. data/lib/rubycord/voice/encoder.rb +113 -0
  76. data/lib/rubycord/voice/network.rb +366 -0
  77. data/lib/rubycord/voice/sodium.rb +96 -0
  78. data/lib/rubycord/voice/voice_bot.rb +408 -0
  79. data/lib/rubycord/webhooks/builder.rb +100 -0
  80. data/lib/rubycord/webhooks/client.rb +132 -0
  81. data/lib/rubycord/webhooks/embeds.rb +248 -0
  82. data/lib/rubycord/webhooks/modal.rb +78 -0
  83. data/lib/rubycord/webhooks/version.rb +7 -0
  84. data/lib/rubycord/webhooks/view.rb +192 -0
  85. data/lib/rubycord/webhooks.rb +12 -0
  86. data/lib/rubycord/websocket.rb +70 -0
  87. data/lib/rubycord.rb +140 -0
  88. metadata +231 -0
@@ -0,0 +1,37 @@
1
+ module Rubycord
2
+ # Mixin for objects that have IDs
3
+ module IDObject
4
+ # @return [Integer] the ID which uniquely identifies this object across Discord.
5
+ attr_reader :id
6
+ alias_method :resolve_id, :id
7
+ alias_method :hash, :id
8
+
9
+ # ID based comparison
10
+ def ==(other)
11
+ Rubycord.id_compare(@id, other)
12
+ end
13
+
14
+ alias_method :eql?, :==
15
+
16
+ # Estimates the time this object was generated on based on the beginning of the ID. This is fairly accurate but
17
+ # shouldn't be relied on as Discord might change its algorithm at any time
18
+ # @return [Time] when this object was created at
19
+ def creation_time
20
+ # Milliseconds
21
+ ms = (@id >> 22) + DISCORD_EPOCH
22
+ Time.at(ms / 1000.0)
23
+ end
24
+
25
+ # Creates an artificial snowflake at the given point in time. Useful for comparing against.
26
+ # @param time [Time] The time the snowflake should represent.
27
+ # @return [Integer] a snowflake with the timestamp data as the given time
28
+ def self.synthesise(time)
29
+ ms = (time.to_f * 1000).to_i
30
+ (ms - DISCORD_EPOCH) << 22
31
+ end
32
+
33
+ class << self
34
+ alias_method :synthesize, :synthesise
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ require "rubycord/data"
2
+
3
+ module Rubycord::Light
4
+ # Represents the bot account used for the light bot, but without any methods to change anything.
5
+ class LightProfile
6
+ include Rubycord::IDObject
7
+ include Rubycord::UserAttributes
8
+
9
+ # @!visibility private
10
+ def initialize(data, bot)
11
+ @bot = bot
12
+
13
+ @username = data["username"]
14
+ @id = data["id"].to_i
15
+ @discriminator = data["discriminator"]
16
+ @avatar_id = data["avatar"]
17
+
18
+ @bot_account = false
19
+ @bot_account = true if data["bot"]
20
+
21
+ @verified = data["verified"]
22
+
23
+ @email = data["email"]
24
+ end
25
+ end
26
+
27
+ # A server that only has an icon, a name, and an ID associated with it, like for example an integration's server.
28
+ class UltraLightServer
29
+ include Rubycord::IDObject
30
+ include Rubycord::ServerAttributes
31
+
32
+ # @!visibility private
33
+ def initialize(data, bot)
34
+ @bot = bot
35
+
36
+ @id = data["id"].to_i
37
+
38
+ @name = data["name"]
39
+ @icon_id = data["icon"]
40
+ end
41
+ end
42
+
43
+ # Represents a light server which only has a fraction of the properties of any other server.
44
+ class LightServer < UltraLightServer
45
+ # @return [true, false] whether or not the LightBot this server belongs to is the owner of the server.
46
+ attr_reader :bot_is_owner
47
+ alias_method :bot_is_owner?, :bot_is_owner
48
+
49
+ # @return [Rubycord::Permissions] the permissions the LightBot has on this server
50
+ attr_reader :bot_permissions
51
+
52
+ # @!visibility private
53
+ def initialize(data, bot)
54
+ super
55
+
56
+ @bot_is_owner = data["owner"]
57
+ @bot_permissions = Rubycord::Permissions.new(data["permissions"])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,71 @@
1
+ require "rubycord/data"
2
+ require "rubycord/light/data"
3
+
4
+ module Rubycord::Light
5
+ # A connection of your Discord account to a particular other service (currently, Twitch and YouTube)
6
+ class Connection
7
+ # @return [Symbol] what type of connection this is (either :twitch or :youtube currently)
8
+ attr_reader :type
9
+
10
+ # @return [true, false] whether this connection is revoked
11
+ attr_reader :revoked
12
+ alias_method :revoked?, :revoked
13
+
14
+ # @return [String] the name of the connected account
15
+ attr_reader :name
16
+
17
+ # @return [String] the ID of the connected account
18
+ attr_reader :id
19
+
20
+ # @return [Array<Integration>] the integrations associated with this connection
21
+ attr_reader :integrations
22
+
23
+ # @!visibility private
24
+ def initialize(data, bot)
25
+ @bot = bot
26
+
27
+ @revoked = data["revoked"]
28
+ @type = data["type"].to_sym
29
+ @name = data["name"]
30
+ @id = data["id"]
31
+
32
+ @integrations = data["integrations"].map { |e| Integration.new(e, self, bot) }
33
+ end
34
+ end
35
+
36
+ # An integration of a connection into a particular server, for example being a member of a subscriber-only Twitch
37
+ # server.
38
+ class Integration
39
+ include Rubycord::IDObject
40
+
41
+ # @return [UltraLightServer] the server associated with this integration
42
+ attr_reader :server
43
+
44
+ # @note The connection returned by this method will have no integrations itself, as Discord doesn't provide that
45
+ # data. Also, it will always be considered not revoked.
46
+ # @return [Connection] the server's underlying connection (for a Twitch subscriber-only server, it would be the
47
+ # Twitch account connection of the server owner).
48
+ attr_reader :server_connection
49
+
50
+ # @return [Connection] the connection integrated with the server (i.e. your connection)
51
+ attr_reader :integrated_connection
52
+
53
+ # @!visibility private
54
+ def initialize(data, integrated, bot)
55
+ @bot = bot
56
+ @integrated_connection = integrated
57
+
58
+ @server = UltraLightServer.new(data["guild"], bot)
59
+
60
+ # Restructure the given data so we can reuse the Connection initializer
61
+ restructured = {}
62
+
63
+ restructured["type"] = data["type"]
64
+ restructured["id"] = data["account"]["id"]
65
+ restructured["name"] = data["account"]["name"]
66
+ restructured["integrations"] = []
67
+
68
+ @server_connection = Connection.new(restructured, bot)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,56 @@
1
+ require "rubycord/api"
2
+ require "rubycord/api/invite"
3
+ require "rubycord/api/user"
4
+ require "rubycord/light/data"
5
+ require "rubycord/light/integrations"
6
+
7
+ # This module contains classes to allow connections to bots without a connection to the gateway socket, i.e. bots
8
+ # that only use the REST part of the API.
9
+ module Rubycord::Light
10
+ # A bot that only uses the REST part of the API. Hierarchically unrelated to the regular {Rubycord::Bot}. Useful to
11
+ # make applications integrated to Discord over OAuth, for example.
12
+ class LightBot
13
+ # Create a new LightBot. This does no networking yet, all networking is done by the methods on this class.
14
+ # @param token [String] The token that should be used to authenticate to Discord. Can be an OAuth token or a regular
15
+ # user account token.
16
+ def initialize(token)
17
+ if token.respond_to? :token
18
+ # Parse AccessTokens from the OAuth2 gem
19
+ token = token.token
20
+ end
21
+
22
+ unless token.include? "."
23
+ # Discord user/bot tokens always contain two dots, so if there's none we can assume it's an OAuth token.
24
+ token = "Bearer #{token}" # OAuth tokens have to be prefixed with 'Bearer' for Discord to be able to use them
25
+ end
26
+
27
+ @token = token
28
+ end
29
+
30
+ # @return [LightProfile] the details of the user this bot is connected to.
31
+ def profile
32
+ response = Rubycord::API::User.profile(@token)
33
+ LightProfile.new(JSON.parse(response), self)
34
+ end
35
+
36
+ # @return [Array<LightServer>] the servers this bot is connected to.
37
+ def servers
38
+ response = Rubycord::API::User.servers(@token)
39
+ JSON.parse(response).map { |e| LightServer.new(e, self) }
40
+ end
41
+
42
+ # Joins a server using an instant invite.
43
+ # @param code [String] The code part of the invite (for example 0cDvIgU2voWn4BaD if the invite URL is
44
+ # https://discord.gg/0cDvIgU2voWn4BaD)
45
+ def join(code)
46
+ Rubycord::API::Invite.accept(@token, code)
47
+ end
48
+
49
+ # Gets the connections associated with this account.
50
+ # @return [Array<Connection>] this account's connections.
51
+ def connections
52
+ response = Rubycord::API::User.connections(@token)
53
+ JSON.parse(response).map { |e| Connection.new(e, self) }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,6 @@
1
+ require "rubycord/light/light_bot"
2
+
3
+ # This module contains classes to allow connections to bots without a connection to the gateway socket, i.e. bots
4
+ # that only use the REST part of the API.
5
+ module Rubycord::Light
6
+ end
@@ -0,0 +1,118 @@
1
+ module Rubycord
2
+ # The format log timestamps should be in, in strftime format
3
+ LOG_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%L"
4
+
5
+ # Logs debug messages
6
+ class Logger
7
+ # @return [true, false] whether this logger is in extra-fancy mode!
8
+ attr_writer :fancy
9
+
10
+ # @return [String, nil] The bot token to be redacted or nil if it shouldn't.
11
+ attr_writer :token
12
+
13
+ # @return [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
14
+ attr_accessor :streams
15
+
16
+ # Creates a new logger.
17
+ # @param fancy [true, false] Whether this logger uses fancy mode (ANSI escape codes to make the output colourful)
18
+ # @param streams [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
19
+ def initialize(fancy = false, streams = [$stdout])
20
+ @fancy = fancy
21
+ self.mode = :normal
22
+
23
+ @streams = streams
24
+ end
25
+
26
+ # The modes this logger can have. This is probably useless unless you want to write your own Logger
27
+ MODES = {
28
+ debug: {long: "DEBUG", short: "D", format_code: ""},
29
+ good: {long: "GOOD", short: "✓", format_code: "\u001B[32m"}, # green
30
+ info: {long: "INFO", short: "i", format_code: ""},
31
+ warn: {long: "WARN", short: "!", format_code: "\u001B[33m"}, # yellow
32
+ error: {long: "ERROR", short: "✗", format_code: "\u001B[31m"}, # red
33
+ out: {long: "OUT", short: "→", format_code: "\u001B[36m"}, # cyan
34
+ in: {long: "IN", short: "←", format_code: "\u001B[35m"}, # purple
35
+ ratelimit: {long: "RATELIMIT", short: "R", format_code: "\u001B[41m"} # red background
36
+ }.freeze
37
+
38
+ # The ANSI format code that resets formatting
39
+ FORMAT_RESET = "\u001B[0m"
40
+
41
+ # The ANSI format code that makes something bold
42
+ FORMAT_BOLD = "\u001B[1m"
43
+
44
+ MODES.each do |mode, hash|
45
+ define_method(mode) do |message|
46
+ write(message.to_s, hash) if @enabled_modes.include? mode
47
+ end
48
+ end
49
+
50
+ # Sets the logging mode to :debug
51
+ # @param value [true, false] Whether debug mode should be on. If it is off the mode will be set to :normal.
52
+ def debug=(value)
53
+ self.mode = value ? :debug : :normal
54
+ end
55
+
56
+ # Sets the logging mode
57
+ # Possible modes are:
58
+ # * :debug logs everything
59
+ # * :verbose logs everything except for debug messages
60
+ # * :normal logs useful information, warnings and errors
61
+ # * :quiet only logs warnings and errors
62
+ # * :silent logs nothing
63
+ # @param value [Symbol] What logging mode to use
64
+ def mode=(value)
65
+ case value
66
+ when :debug
67
+ @enabled_modes = %i[debug good info warn error out in ratelimit]
68
+ when :verbose
69
+ @enabled_modes = %i[good info warn error out in ratelimit]
70
+ when :normal
71
+ @enabled_modes = %i[info warn error ratelimit]
72
+ when :quiet
73
+ @enabled_modes = %i[warn error]
74
+ when :silent
75
+ @enabled_modes = %i[]
76
+ end
77
+ end
78
+
79
+ # Logs an exception to the console.
80
+ # @param e [Exception] The exception to log.
81
+ def log_exception(e)
82
+ error("Exception: #{e.inspect}")
83
+ e.backtrace.each { |line| error(line) }
84
+ end
85
+
86
+ private
87
+
88
+ def write(message, mode)
89
+ thread_name = Thread.current[:rubycord_name]
90
+ timestamp = Time.now.strftime(LOG_TIMESTAMP_FORMAT)
91
+
92
+ # Redact token if set
93
+ log = if @token && @token != ""
94
+ message.to_s.gsub(@token, "REDACTED_TOKEN")
95
+ else
96
+ message.to_s
97
+ end
98
+
99
+ @streams.each do |stream|
100
+ if @fancy && !stream.is_a?(File)
101
+ fancy_write(stream, log, mode, thread_name, timestamp)
102
+ else
103
+ simple_write(stream, log, mode, thread_name, timestamp)
104
+ end
105
+ end
106
+ end
107
+
108
+ def fancy_write(stream, message, mode, thread_name, timestamp)
109
+ stream.puts "#{timestamp} #{FORMAT_BOLD}#{thread_name.ljust(16)}#{FORMAT_RESET} #{mode[:format_code]}#{mode[:short]}#{FORMAT_RESET} #{message}"
110
+ stream.flush
111
+ end
112
+
113
+ def simple_write(stream, message, mode, thread_name, timestamp)
114
+ stream.puts "[#{mode[:long]} : #{thread_name} @ #{timestamp}] #{message}"
115
+ stream.flush
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,55 @@
1
+ module Rubycord
2
+ # Utility class for wrapping paginated endpoints. It is [Enumerable](https://ruby-doc.org/core-2.5.1/Enumerable.html),
3
+ # similar to an `Array`, so most of the same methods can be used to filter the results of the request
4
+ # that it wraps. If you simply want an array of all of the results, `#to_a` can be called.
5
+ class Paginator
6
+ include Enumerable
7
+
8
+ # Creates a new {Paginator}
9
+ # @param limit [Integer] the maximum number of items to request before stopping
10
+ # @param direction [:up, :down] the order in which results are returned in
11
+ # @yield [Array, nil] the last page of results, or nil if this is the first iteration.
12
+ # This should be used to request the next page of results.
13
+ # @yieldreturn [Array] the next page of results
14
+ def initialize(limit, direction, &block)
15
+ @count = 0
16
+ @limit = limit
17
+ @direction = direction
18
+ @block = block
19
+ end
20
+
21
+ # Yields every item produced by the wrapped request, until it returns
22
+ # no more results or the configured `limit` is reached.
23
+ def each
24
+ last_page = nil
25
+ until limit_check
26
+ page = @block.call(last_page)
27
+ return if page.empty?
28
+
29
+ enumerator = case @direction
30
+ when :down
31
+ page.each
32
+ when :up
33
+ page.reverse_each
34
+ end
35
+
36
+ enumerator.each do |item|
37
+ yield item
38
+ @count += 1
39
+ break if limit_check
40
+ end
41
+
42
+ last_page = page
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Whether the paginator limit has been exceeded
49
+ def limit_check
50
+ return false if @limit.nil?
51
+
52
+ @count >= @limit
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,251 @@
1
+ module Rubycord
2
+ # List of permissions Discord uses
3
+ class Permissions
4
+ # This hash maps bit positions to logical permissions.
5
+ # see https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
6
+ FLAGS = {
7
+ # Bit => Permission # Value
8
+ 0 => :create_instant_invite, # 1
9
+ 1 => :kick_members, # 2
10
+ 2 => :ban_members, # 4
11
+ 3 => :administrator, # 8
12
+ 4 => :manage_channels, # 16
13
+ 5 => :manage_server, # 32
14
+ 6 => :add_reactions, # 64
15
+ 7 => :view_audit_log, # 128
16
+ 8 => :priority_speaker, # 256
17
+ 9 => :stream, # 512
18
+ 10 => :view_channel, # 1024 (which includes reading messages in text channels and joining voice channels)
19
+ 11 => :send_messages, # 2048
20
+ 12 => :send_tts_messages, # 4096
21
+ 13 => :manage_messages, # 8192
22
+ 14 => :embed_links, # 16384
23
+ 15 => :attach_files, # 32768
24
+ 16 => :read_message_history, # 65536
25
+ 17 => :mention_everyone, # 131072
26
+ 18 => :use_external_emojis, # 262144
27
+ 19 => :view_server_insights, # 524288
28
+ 20 => :connect, # 1048576
29
+ 21 => :speak, # 2097152
30
+ 22 => :mute_members, # 4194304
31
+ 23 => :deafen_members, # 8388608
32
+ 24 => :move_members, # 16777216
33
+ 25 => :use_vad, # 33554432 (voice-activity-detection in a voice channel)
34
+ 26 => :change_nickname, # 67108864
35
+ 27 => :manage_nicknames, # 134217728
36
+ 28 => :manage_roles, # 268435456 (roles & roles permissions)
37
+ 29 => :manage_webhooks, # 536870912
38
+ 30 => :manage_server_expressions, # 1073741824 (emojis, stickers, and soundboard)
39
+ 31 => :use_application_commands, # 2147483648 (to use application commands, including slash commands and context menu commands)
40
+ 32 => :request_to_speak, # 4294967296
41
+ 33 => :manage_events, # 8589934592
42
+ 34 => :manage_threads, # 17179869184
43
+ 35 => :create_public_threads, # 34359738368 (for creating public and announcement threads)
44
+ 36 => :create_private_threads, # 68719476736
45
+ 37 => :use_external_stickers, # 137438953472
46
+ 38 => :send_messages_in_threads, # 274877906944
47
+ 39 => :use_embedded_activities, # 549755813888
48
+ 40 => :moderate_members, # 1099511627776
49
+ 41 => :view_creator_monetization_analytics, # 2199023255552
50
+ 42 => :use_soundboard, # 4398046511104
51
+ 43 => :create_server_expressions, # 8796093022208 (for creating emojis, stickers, and soundboard sounds, and editing and deleting)
52
+ 44 => :create_events, # 17592186044416 (for creating scheduled events, and editing and deleting those created by the current user)
53
+ 45 => :use_external_sounds, # 35184372088832
54
+ 46 => :send_voice_messages, # 70368744177664
55
+ 49 => :send_polls, # 562949953421312
56
+ 50 => :use_external_apps # 1125899906842624
57
+ }.freeze
58
+
59
+ FLAGS.each do |position, flag|
60
+ attr_reader flag
61
+
62
+ define_method :"can_#{flag}=" do |value|
63
+ new_bits = @bits
64
+ if value
65
+ new_bits |= (1 << position)
66
+ else
67
+ new_bits &= ~(1 << position)
68
+ end
69
+ @writer&.write(new_bits)
70
+ @bits = new_bits
71
+ init_vars
72
+ end
73
+ end
74
+
75
+ alias_method :can_administrate=, :can_administrator=
76
+ alias_method :administrate, :administrator
77
+
78
+ attr_reader :bits
79
+
80
+ # Set the raw bitset of this permission object
81
+ # @param bits [Integer] A number whose binary representation is the desired bitset.
82
+ def bits=(bits)
83
+ @bits = bits
84
+ init_vars
85
+ end
86
+
87
+ # Initialize the instance variables based on the bitset.
88
+ def init_vars
89
+ FLAGS.each do |position, flag|
90
+ flag_set = ((@bits >> position) & 0x1) == 1
91
+ instance_variable_set :"@#{flag}", flag_set
92
+ end
93
+ end
94
+
95
+ # Return the corresponding bits for an array of permission flag symbols.
96
+ # This is a class method that can be used to calculate bits instead
97
+ # of instancing a new Permissions object.
98
+ # @example Get the bits for permissions that could allow/deny read messages, connect, and speak
99
+ # Permissions.bits [:view_channel, :connect, :speak] #=> 3146752
100
+ # @param list [Array<Symbol>]
101
+ # @return [Integer] the computed permissions integer
102
+ def self.bits(list)
103
+ value = 0
104
+
105
+ FLAGS.each do |position, flag|
106
+ value += 2**position if list.include? flag
107
+ end
108
+
109
+ value
110
+ end
111
+
112
+ # Create a new Permissions object either as a blank slate to add permissions to (for example for
113
+ # {Channel#define_overwrite}) or from existing bit data to read out.
114
+ # @example Create a permissions object that could allow/deny read messages, connect, and speak by setting flags
115
+ # permission = Permissions.new
116
+ # permission.can_view_channel = true
117
+ # permission.can_connect = true
118
+ # permission.can_speak = true
119
+ # @example Create a permissions object that could allow/deny read messages, connect, and speak by an array of symbols
120
+ # Permissions.new [:view_channel, :connect, :speak]
121
+ # @param bits [String, Integer, Array<Symbol>] The permission bits that should be set from the beginning, or an array of permission flag symbols
122
+ # @param writer [RoleWriter] The writer that should be used to update data when a permission is set.
123
+ def initialize(bits = 0, writer = nil)
124
+ @writer = writer
125
+
126
+ @bits = if bits.is_a? Array
127
+ self.class.bits(bits)
128
+ else
129
+ bits.to_i
130
+ end
131
+
132
+ init_vars
133
+ end
134
+
135
+ # Return an array of permission flag symbols for this class's permissions
136
+ # @example Get the permissions for the bits "9"
137
+ # permissions = Permissions.new(9)
138
+ # permissions.defined_permissions #=> [:create_instant_invite, :administrator]
139
+ # @return [Array<Symbol>] the permissions
140
+ def defined_permissions
141
+ FLAGS.filter_map { |value, name| (@bits & (1 << value)).positive? ? name : nil }
142
+ end
143
+
144
+ # Comparison based on permission bits
145
+ def ==(other)
146
+ false unless other.is_a? Rubycord::Permissions
147
+ bits == other.bits
148
+ end
149
+ end
150
+
151
+ # Mixin to calculate resulting permissions from overrides etc.
152
+ module PermissionCalculator
153
+ # Checks whether this user can do the particular action, regardless of whether it has the permission defined,
154
+ # through for example being the server owner or having the Manage Roles permission
155
+ # @param action [Symbol] The permission that should be checked. See also {Permissions::FLAGS} for a list.
156
+ # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
157
+ # @example Check if the bot can send messages to a specific channel in a server.
158
+ # bot_profile = bot.profile.on(event.server)
159
+ # can_send_messages = bot_profile.permission?(:send_messages, channel)
160
+ # @return [true, false] whether or not this user has the permission.
161
+ def permission?(action, channel = nil)
162
+ # If the member is the server owner, it irrevocably has all permissions.
163
+ return true if owner?
164
+
165
+ # First, check whether the user has Manage Roles defined.
166
+ # (Coincidentally, Manage Permissions is the same permission as Manage Roles, and a
167
+ # Manage Permissions deny overwrite will override Manage Roles, so we can just check for
168
+ # Manage Roles once and call it a day.)
169
+ return true if defined_permission?(:administrator, channel)
170
+
171
+ # Otherwise, defer to defined_permission
172
+ defined_permission?(action, channel)
173
+ end
174
+
175
+ # Checks whether this user has a particular permission defined (i.e. not implicit, through for example
176
+ # Manage Roles)
177
+ # @param action [Symbol] The permission that should be checked. See also {Permissions::FLAGS} for a list.
178
+ # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
179
+ # @example Check if a member has the Manage Channels permission defined in the server.
180
+ # has_manage_channels = member.defined_permission?(:manage_channels)
181
+ # @return [true, false] whether or not this user has the permission defined.
182
+ def defined_permission?(action, channel = nil)
183
+ # For slash commands we may not have access to the server or role
184
+ # permissions. In this case we use the permissions given to us by the
185
+ # interaction. If attempting to check against a specific channel the check
186
+ # is skipped.
187
+ return @permissions.__send__(action) if @permissions && channel.nil?
188
+
189
+ # Get the permission the user's roles have
190
+ role_permission = defined_role_permission?(action, channel)
191
+
192
+ # Once we have checked the role permission, we have to check the channel overrides for the
193
+ # specific user
194
+ user_specific_override = permission_overwrite(action, channel, id) # Use the ID reader as members have no ID instance variable
195
+
196
+ # Merge the two permissions - if an override is defined, it has to be allow, otherwise we only care about the role
197
+ return role_permission unless user_specific_override
198
+
199
+ user_specific_override == :allow
200
+ end
201
+
202
+ # Define methods for querying permissions
203
+ Rubycord::Permissions::FLAGS.each_value do |flag|
204
+ define_method :"can_#{flag}?" do |channel = nil|
205
+ permission? flag, channel
206
+ end
207
+ end
208
+
209
+ alias_method :can_administrate?, :can_administrator?
210
+
211
+ private
212
+
213
+ def defined_role_permission?(action, channel)
214
+ roles_to_check = [@server.everyone_role] + roles
215
+
216
+ # For each role, check if
217
+ # (1) the channel explicitly allows or permits an action for the role and
218
+ # (2) if the user is allowed to do the action if the channel doesn't specify
219
+ roles_to_check.sort_by(&:position).reduce(false) do |can_act, role|
220
+ # Get the override defined for the role on the channel
221
+ channel_allow = permission_overwrite(action, channel, role.id)
222
+ if channel_allow
223
+ # If the channel has an override, check whether it is an allow - if yes,
224
+ # the user can act, if not, it can't
225
+ break true if channel_allow == :allow
226
+
227
+ false
228
+ else
229
+ # Otherwise defer to the role
230
+ role.permissions.instance_variable_get(:"@#{action}") || can_act
231
+ end
232
+ end
233
+ end
234
+
235
+ def permission_overwrite(action, channel, id)
236
+ # If no overwrites are defined, or no channel is set, no overwrite will be present
237
+ return nil unless channel && channel.permission_overwrites[id]
238
+
239
+ # Otherwise, check the allow and deny objects
240
+ allow = channel.permission_overwrites[id].allow
241
+ deny = channel.permission_overwrites[id].deny
242
+ if allow.instance_variable_get(:"@#{action}")
243
+ :allow
244
+ elsif deny.instance_variable_get(:"@#{action}")
245
+ :deny
246
+ end
247
+
248
+ # If there's no variable defined, nil will implicitly be returned
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,5 @@
1
+ # Rubycord and all its functionality, in this case only the version.
2
+ module Rubycord
3
+ # The current version of rubycord.
4
+ VERSION = "1.0.0" unless const_defined?(:VERSION)
5
+ end