discorb-lib/discorb

View on GitHub
lib/discorb/member.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Discorb
  #
  # Represents a member of a guild.
  #
  class Member < User
    # @return [Time] The time the member boosted the guild.
    attr_reader :premium_since
    # @return [String] The nickname of the member.
    # @return [nil] If the member has no nickname.
    attr_reader :nick
    # @return [Time] The time the member joined the guild.
    attr_reader :joined_at
    # @return [Discorb::Asset] The custom avatar of the member.
    # @return [nil] If the member has no custom avatar.
    attr_reader :custom_avatar
    # @return [Discorb::Asset] The display avatar of the member.
    attr_reader :display_avatar
    # @return [Boolean] Whether the member is muted.
    attr_reader :mute
    alias mute? mute
    # @return [Boolean] Whether the member is deafened.
    attr_reader :deaf
    alias deaf? deaf
    # @return [Boolean] Whether the member is pending (Not passed member screening).
    attr_reader :pending
    alias pending? pending

    # @!attribute [r] name
    #   @return [String] The display name of the member.
    # @!attribute [r] mention
    #   @return [String] The mention of the member.
    # @!attribute [r] voice_state
    #   @return [Discorb::VoiceState] The voice state of the member.
    # @!attribute [r] roles
    #   @macro client_cache
    #   @return [Array<Discorb::Role>] The roles of the member.
    # @!attribute [r] guild
    #   @macro client_cache
    #   @return [Discorb::Guild] The guild the member is in.
    # @!attribute [r] hoisted_role
    #   @macro client_cache
    #   @return [Discorb::Role] The hoisted role of the member.
    #   @return [nil] If the member has no hoisted role.
    # @!attribute [r] hoisted?
    #   @return [Boolean] Whether the member has a hoisted role.
    # @!attribute [r] permissions
    #   @return [Discorb::Permission] The permissions of the member.
    # @!attribute [r] presence
    #   @macro client_cache
    #   @return [Discorb::Presence] The presence of the member.
    # @!attribute [r] activity
    #   @macro client_cache
    #   @return [Discorb::Presence::Activity] The activity of the member. It's from the {#presence}.
    # @!attribute [r] activities
    #   @macro client_cache
    #   @return [Array<Discorb::Presence::Activity>] The activities of the member. It's from the {#presence}.
    # @!attribute [r] status
    #   @macro client_cache
    #   @return [Symbol] The status of the member. It's from the {#presence}.
    # @!attribute [r] owner?
    #   @return [Boolean] Whether the member is the owner of the guild.

    #
    # Initialize a new instance of the member.
    # @private
    #
    # @param [Discorb::Client] client The client.
    # @param [Discorb::Snowflake] guild_id The ID of the guild.
    # @param [Hash] user_data The data of the user.
    # @param [Hash] member_data The data of the member.
    #
    def initialize(client, guild_id, user_data, member_data)
      @guild_id = guild_id
      @client = client
      @_member_data = {}
      @data = {}
      _set_data(user_data, member_data)
    end

    #
    # Format the user as `Display name (@Username)` or `Display name#Discriminator` style.
    #
    # @return [String] The formatted member.
    #
    def to_s
      if @discriminator == "0"
        "#{name} (@#{@username})"
      else
        "#{username}##{discriminator}"
      end
    end

    def name
      @nick || @global_name || @username
    end

    def mention
      "<@#{@nick.nil? ? "" : "!"}#{@id}>"
    end

    def voice_state
      guild.voice_states[@id]
    end

    def owner?
      guild.owner_id == @id
    end

    def guild
      @client.guilds[@guild_id]
    end

    def roles
      @role_ids.map { |r| guild.roles[r] }.sort_by(&:position).reverse +
        [guild.roles[guild.id]]
    end

    def permissions
      return Permission.new((1 << 38) - 1) if owner?

      roles.map(&:permissions).sum(Permission.new(0))
    end

    alias guild_permissions permissions

    def hoisted_role
      @hoisted_role_id && guild.roles[@hoisted_role_id]
    end

    def hoisted?
      !@hoisted_role_id.nil?
    end

    def presence
      guild.presences[@id]
    end

    def activity
      presence&.activity
    end

    def activities
      presence&.activities
    end

    def status
      presence&.status
    end

    def inspect
      "#<#{self.class} #{self} id=#{@id}>"
    end

    #
    # Add a role to the member.
    # @async
    #
    # @param [Discorb::Role] role The role to add.
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<void>] The task.
    #
    def add_role(role, reason: nil)
      Async do
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@guild_id}/members/#{@id}/roles/#{role.is_a?(Role) ? role.id : role}",
              "//guilds/:guild_id/members/:user_id/roles/:role_id",
              :put
            ),
            nil,
            audit_log_reason: reason
          )
          .wait
      end
    end

    #
    # Remove a role to the member.
    # @async
    #
    # @param [Discorb::Role] role The role to add.
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<void>] The task.
    #
    def remove_role(role, reason: nil)
      Async do
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@guild_id}/members/#{@id}/roles/#{role.is_a?(Role) ? role.id : role}",
              "//guilds/:guild_id/members/:user_id/roles/:role_id",
              :delete
            ),
            {},
            audit_log_reason: reason
          )
          .wait
      end
    end

    #
    # Edit the member.
    # @async
    # @macro edit
    #
    # @param [String] nick The nickname of the member.
    # @param [Discorb::Role] role The roles of the member.
    # @param [Boolean] mute Whether the member is muted.
    # @param [Boolean] deaf Whether the member is deafened.
    # @param [Discorb::StageChannel] channel The channel the member is moved to.
    # @param [Time, nil] communication_disabled_until The time the member is timed out. Set to `nil` to end the timeout.
    # @param [Time, nil] timeout_until Alias of `communication_disabled_until`.
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<void>] The task.
    #
    def edit(
      nick: Discorb::Unset,
      role: Discorb::Unset,
      mute: Discorb::Unset,
      deaf: Discorb::Unset,
      channel: Discorb::Unset,
      communication_disabled_until: Discorb::Unset,
      timeout_until: Discorb::Unset,
      reason: nil
    )
      Async do
        payload = {}
        payload[:nick] = nick if nick != Discorb::Unset
        payload[:roles] = role if role != Discorb::Unset
        payload[:mute] = mute if mute != Discorb::Unset
        payload[:deaf] = deaf if deaf != Discorb::Unset
        communication_disabled_until = timeout_until if timeout_until !=
          Discorb::Unset
        if communication_disabled_until != Discorb::Unset
          payload[
            :communication_disabled_until
          ] = communication_disabled_until&.iso8601
        end
        payload[:channel_id] = channel&.id if channel != Discorb::Unset
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@guild_id}/members/#{@id}",
              "//guilds/:guild_id/members/:user_id",
              :patch
            ),
            payload,
            audit_log_reason: reason
          )
          .wait
      end
    end

    alias modify edit

    #
    # Timeout the member.
    # @async
    #
    # @param [Time] time The time until the member is timeout.
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<void>] The task.
    #
    def timeout(time, reason: nil)
      edit(communication_disabled_until: time, reason:)
    end

    alias disable_communication timeout

    #
    # Kick the member.
    # @async
    #
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<void>] The task.
    #
    def kick(reason: nil)
      Async { guild.kick_member(self, reason:).wait }
    end

    #
    # Ban the member.
    # @async
    #
    # @param [Integer] delete_message_days The number of days to delete messages.
    # @param [String] reason The reason for the action.
    #
    # @return [Async::Task<Discorb::Guild::Ban>] The ban.
    #
    def ban(delete_message_days: 0, reason: nil)
      Async do
        guild.ban_member(
          self,
          delete_message_days:,
          reason:
        ).wait
      end
    end

    #
    # Checks if the member can manage the given role.
    #
    # @param [Discorb::Role] role The role.
    #
    # @return [Boolean] `true` if the member can manage the role.
    #
    def can_manage?(role)
      return true if owner?

      top_role = roles.max_by(&:position)
      top_role.position > role.position
    end

    private

    def _set_data(user_data, member_data)
      user_data ||= member_data[:user]
      @role_ids = member_data[:roles]
      @premium_since =
        member_data[:premium_since] && Time.iso8601(member_data[:premium_since])
      @pending = member_data[:pending]
      @nick = member_data[:nick]
      @mute = member_data[:mute]
      @joined_at =
        member_data[:joined_at] && Time.iso8601(member_data[:joined_at])
      @hoisted_role_id = member_data[:hoisted_role]
      @deaf = member_data[:deaf]
      @custom_avatar =
        member_data[:avatar] && Asset.new(self, member_data[:avatar])
      super(user_data)
      @display_avatar = @custom_avatar || @avatar
      @client.guilds[@guild_id].members[@id] = self unless @guild_id.nil? ||
        @client.guilds[@guild_id].nil?
      @_member_data.update(member_data)
    end
  end
end