discorb-lib/discorb

View on GitHub
lib/discorb/permission.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Discorb
  #
  # Represents a permission per guild.
  # ## Flag fields
  #
  # | Field | Value |
  # |-------|-------|
  # |`1 << 0`|`:create_instant_invite`|
  # |`1 << 1`|`:kick_members`|
  # |`1 << 2`|`:ban_members`|
  # |`1 << 3`|`:administrator`|
  # |`1 << 4`|`:manage_channels`|
  # |`1 << 5`|`:manage_guild`|
  # |`1 << 6`|`:add_reactions`|
  # |`1 << 7`|`:view_audit_log`|
  # |`1 << 8`|`:priority_speaker`|
  # |`1 << 9`|`:stream`|
  # |`1 << 10`|`:view_channel`|
  # |`1 << 11`|`:send_messages`|
  # |`1 << 12`|`:send_tts_messages`|
  # |`1 << 13`|`:manage_messages`|
  # |`1 << 14`|`:embed_links`|
  # |`1 << 15`|`:attach_files`|
  # |`1 << 16`|`:read_message_history`|
  # |`1 << 17`|`:mention_everyone`|
  # |`1 << 18`|`:use_external_emojis`|
  # |`1 << 19`|`:view_guild_insights`|
  # |`1 << 20`|`:connect`|
  # |`1 << 21`|`:speak`|
  # |`1 << 22`|`:mute_members`|
  # |`1 << 23`|`:deafen_members`|
  # |`1 << 24`|`:move_members`|
  # |`1 << 25`|`:use_vad`|
  # |`1 << 26`|`:change_nickname`|
  # |`1 << 27`|`:manage_nicknames`|
  # |`1 << 28`|`:manage_roles`|
  # |`1 << 29`|`:manage_webhooks`|
  # |`1 << 30`|`:manage_emojis`|
  # |`1 << 31`|`:use_slash_commands`|
  # |`1 << 32`|`:request_to_speak`|
  # |`1 << 34`|`:manage_threads`|
  # |`1 << 35`|`:use_public_threads`|
  # |`1 << 36`|`:use_private_threads`|
  #
  class Permission < Flag
    @bits = {
      create_instant_invite: 0,
      kick_members: 1,
      ban_members: 2,
      administrator: 3,
      manage_channels: 4,
      manage_guild: 5,
      add_reactions: 6,
      view_audit_log: 7,
      priority_speaker: 8,
      stream: 9,
      view_channel: 10,
      send_messages: 11,
      send_tts_messages: 12,
      manage_messages: 13,
      embed_links: 14,
      attach_files: 15,
      read_message_history: 16,
      mention_everyone: 17,
      use_external_emojis: 18,
      view_guild_insights: 19,
      connect: 20,
      speak: 21,
      mute_members: 22,
      deafen_members: 23,
      move_members: 24,
      use_vad: 25,
      change_nickname: 26,
      manage_nicknames: 27,
      manage_roles: 28,
      manage_webhooks: 29,
      manage_emojis: 30,
      use_slash_commands: 31,
      request_to_speak: 32,
      manage_threads: 34,
      use_public_threads: 35,
      use_private_threads: 36,
      use_external_stickers: 37,
      send_messages_in_threads: 38,
      use_embedded_activities: 39,
      moderate_members: 40
    }.freeze
  end

  #
  # Represents a permission per channel.
  #
  class PermissionOverwrite
    # @!attribute [r] allow
    #   @return [Discorb::Permission] The allowed permissions.
    # @!attribute [r] deny
    #   @return [Discorb::Permission] The denied permissions.
    # @!attribute [r] allow_value
    #   @return [Integer] The allowed permissions as an integer.
    # @!attribute [r] deny_value
    #   @return [Integer] The denied permissions as an integer.

    @raw_bits = {
      create_instant_invite: 0,
      kick_members: 1,
      ban_members: 2,
      administrator: 3,
      manage_channels: 4,
      manage_guild: 5,
      add_reactions: 6,
      view_audit_log: 7,
      priority_speaker: 8,
      stream: 9,
      view_channel: 10,
      send_messages: 11,
      send_tts_messages: 12,
      manage_messages: 13,
      embed_links: 14,
      attach_files: 15,
      read_message_history: 16,
      mention_everyone: 17,
      use_external_emojis: 18,
      view_guild_insights: 19,
      connect: 20,
      speak: 21,
      mute_members: 22,
      deafen_members: 23,
      move_members: 24,
      use_vad: 25,
      change_nickname: 26,
      manage_nicknames: 27,
      manage_roles: 28,
      manage_webhooks: 29,
      manage_emojis: 30,
      use_slash_commands: 31,
      request_to_speak: 32,
      manage_threads: 34,
      use_public_threads: 35,
      use_private_threads: 36
    }.freeze
    @bits = @raw_bits.transform_values { |v| 1 << v }.freeze

    #
    # Initializes a new PermissionOverwrite.
    # @private
    #
    # @param allow [Integer] The allowed permissions.
    # @param deny [Integer] The denied permissions.
    #
    def initialize(allow, deny)
      @allow = allow
      @deny = deny
    end

    def allow
      self.class.bits.keys.filter do |field|
        @allow & self.class.bits[field] != 0
      end
    end

    alias +@ allow

    def deny
      self.class.bits.keys.filter do |field|
        @deny & self.class.bits[field] != 0
      end
    end

    alias -@ deny

    def allow_value
      @allow
    end

    def deny_value
      @deny
    end

    def inspect
      "#<#{self.class} allow=#{allow} deny=#{deny}>"
    end

    #
    # Converts the permission overwrite to a hash.
    #
    # @return [Hash] The permission overwrite as a hash.
    #
    def to_hash
      self.class.bits.keys.to_h do |field|
        [
          field,
          if @allow & self.class.bits[field] != 0
            true
          elsif @deny & self.class.bits[field] != 0
            false
          end
        ]
      end
    end

    #
    # Union of the permission overwrites.
    #
    # @param [Discorb::PermissionOverwrite] other The other permission overwrite.
    #
    # @return [Discorb::PermissionOverwrite] The union of the permission overwrites.
    #
    def +(other)
      result = to_hash
      self.class.bits.each_key do |field|
        next if other[field].nil?
        result[field] = (
          other[field] ||
            raise(KeyError, "field #{field} not found in #{other.inspect}")
        )
      end
      self.class.from_hash(result)
    end

    #
    # Returns whether overwrite of the given field.
    #
    # @param [Symbol] field The field to check.
    #
    # @return [true, false, nil] Whether the field is allowed, denied or not set.
    #
    def [](field)
      if @allow & self.class.bits[field] != 0
        true
      elsif @deny & self.class.bits[field] != 0
        false
      end
    end

    #
    # Sets the given field to the given value.
    #
    # @param [Symbol] key The field to set.
    # @param [Boolean] bool The value to set.
    #
    def []=(key, bool)
      case bool
      when true
        @allow |= self.class.bits[key]
        @deny &= ~self.class.bits[key]
      when false
        @allow &= ~self.class.bits[key]
        @deny |= self.class.bits[key]
      else
        @allow &= ~self.class.bits[key]
        @deny &= ~self.class.bits[key]
      end
    end

    #
    # (see Flag#method_missing)
    #
    def method_missing(method, bool = nil)
      if self.class.bits.key?(method)
        self[method]
      elsif self.class.bits.key?(method.to_s.delete_suffix("=").to_sym)
        key = method.to_s.delete_suffix("=").to_sym
        self[key] = bool
      else
        super
      end
    end

    def respond_to_missing?(method, _arg)
      self.class.bits.key?(method.to_s.delete_suffix("=").to_sym) ? true : super
    end

    class << self
      # @private
      attr_reader :bits

      #
      # Initializes a permission overwrite from a hash.
      #
      # @param [Hash] hash The hash to initialize the permission overwrite from.
      #
      # @return [Discorb::PermissionOverwrite] The permission overwrite.
      #
      def from_hash(hash)
        allow = 0
        deny = 0
        hash
          .filter do |k, v|
            self.class.bits.keys.include?(k) && [true, false].include?(v)
          end
          .each do |k, v|
            if v
              allow += self.class.bits[k]
            else
              deny += self.class.bits[k]
            end
          end

        new(allow, deny)
      end
    end
  end
end