discorb-lib/discorb

View on GitHub
lib/discorb/guild.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Discorb
  #
  # Represents a guild in the Discord.
  #
  class Guild < DiscordModel
    # @return [Discorb::Snowflake] ID of the guild.
    attr_reader :id
    # @return [String] The name of the guild.
    attr_reader :name
    # @return [Discorb::Asset] The splash of the guild.
    attr_reader :splash
    # @return [Discorb::Asset] The discovery splash of the guild.
    attr_reader :discovery_splash
    # @return [Discorb::Snowflake] ID of the guild owner.
    attr_reader :owner_id
    # @return [Discorb::Permission] The bot's permission in the guild.
    attr_reader :permissions
    # @return [Integer] The AFK timeout of the guild.
    attr_reader :afk_timeout
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Role}] A dictionary of roles in the guild.
    attr_reader :roles
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::CustomEmoji}]
    #   A dictionary of custom emojis in the guild.
    attr_reader :emojis
    # @return [Array<Symbol>] features that are enabled in the guild.
    # @see https://discord.com/developers/docs/resources/guild#guild-object-guild-features Official Discord API docs
    attr_reader :features
    # @return [:none, :elevated] The MFA level of the guild.
    attr_reader :mfa_level
    # @return [Discorb::SystemChannelFlag] The flag for the system channel.
    attr_reader :system_channel_flags
    # @return [Time] Time that representing when bot has joined the guild.
    attr_reader :joined_at
    # @return [Boolean] Whether the guild is unavailable.
    attr_reader :unavailable
    # @return [Integer] The amount of members in the guild.
    attr_reader :member_count
    # @return [Discorb::Asset] The icon of the guild.
    attr_reader :icon
    # @return [Discorb::Dictionary{Discorb::User => Discorb::VoiceState}] A dictionary of voice states in the guild.
    attr_reader :voice_states
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Member}] A dictionary of members in the guild.
    # @macro members_intent
    attr_reader :members
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::GuildChannel}] A dictionary of channels in the guild.
    attr_reader :channels
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::ThreadChannel}] A dictionary of threads in the guild.
    attr_reader :threads
    # @return [Discorb::Dictionary{Discorb::User => Discorb::Presence}] A dictionary of presence in the guild.
    attr_reader :presences
    # @return [Integer] Number of online members in the guild.
    attr_reader :max_presences
    # @return [String] The vanity invite URL for the guild.
    # @return [nil] If the guild does not have a vanity invite URL.
    attr_reader :vanity_url_code
    # @return [String] The description of the guild.
    attr_reader :description
    # @return [Discorb::Asset] The banner of the guild.
    # @return [nil] If the guild does not have a banner.
    attr_reader :banner
    # @return [Integer] The premium tier (Boost Level) of the guild.
    attr_reader :premium_tier
    # @return [Integer] The amount of premium subscriptions (Server Boosts) the guild has.
    attr_reader :premium_subscription_count
    # @return [Symbol] The preffered language of the guild.
    # @note This modifies the language code, `-` will be replaced with `_`.
    attr_reader :preferred_locale
    # @return [Integer] The maximum amount of users in a video channel.
    attr_reader :max_video_channel_users
    # @return [Integer] The approxmate amount of members in the guild.
    attr_reader :approximate_member_count
    # @return [Integer] The approxmate amount of non-offline members in the guild.
    attr_reader :approximate_presence_count
    # @return [Discorb::WelcomeScreen] The welcome screen of the guild.
    attr_reader :welcome_screen
    # @return [:default, :explicit, :safe, :age_restricted] The nsfw level of the guild.
    attr_reader :nsfw_level
    # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::StageInstance}]
    #   A dictionary of stage instances in the guild.
    attr_reader :stage_instances
    # @return [:none, :low, :medium, :high, :very_high] The verification level of the guild.
    attr_reader :verification_level
    # @return [:all_messages, :only_mentions] The default message notification level of the guild.
    attr_reader :default_message_notifications
    # @return [:disabled_in_text, :members_without_roles, :all_members] The explict content filter level of the guild.
    attr_reader :explicit_content_filter
    # @return [Boolean] Whether the client is the owner of the guild.
    attr_reader :owner
    alias owner? owner
    # @return [Boolean] Whether the guild is large.
    attr_reader :large
    alias large? large
    # @return [Boolean] Whether the guild enabled the widget.
    attr_reader :widget_enabled
    alias widget_enabled? widget_enabled
    # @return [Boolean] Whether the guild is available.
    attr_reader :available
    alias available? available
    # @return [Dictionary{Discorb::Snowflake => Discorb::ScheduledEvent}] A dictionary of scheduled events in the guild.
    attr_reader :scheduled_events
    alias events scheduled_events

    include Discorb::ChannelContainer

    # @!attribute [r] afk_channel
    #   @return [Discorb::VoiceChannel] The AFK channel for this guild.
    #   @macro client_cache
    # @!attribute [r] system_channel
    #   @return [Discorb::TextChannel] The system message channel for this guild.
    #   @macro client_cache
    # @!attribute [r] rules_channel
    #   @return [Discorb::TextChannel] The rules channel for this guild.
    #   @macro client_cache
    # @!attribute [r] public_updates_channel
    #   @return [Discorb::TextChannel] The public updates channel (`#moderator-only`) for this guild.
    #   @macro client_cache
    # @!attribute [r] me
    #   @return [Discorb::Member] The client's member in the guild.

    # @private
    # @return [Array<Symbol>] The mapping of mfa_level.
    MFA_LEVELS = %i[none elevated].freeze
    # @private
    # @return [Array<Symbol>] The mapping of nsfw_level.
    NSFW_LEVELS = %i[default explicit safe age_restricted].freeze
    # @private
    # @return [Array<Symbol>] The mapping of verification_level.
    VERIFICATION_LEVELS = %i[none low medium high very_high].freeze
    # @private
    # @return [Array<Symbol>] The mapping of default_message_notifications.
    DEFAULT_MESSAGE_NOTIFICATIONS = %i[all_messages only_mentions].freeze
    # @private
    # @return [Array<Symbol>] The mapping of explicit_content_filter.
    EXPLICIT_CONTENT_FILTER = %i[
      disabled_in_text
      members_without_roles
      all_members
    ].freeze

    #
    # Creates a new guild object.
    # @private
    #
    # @param [Discorb::Client] client The client that owns this guild.
    # @param [Hash] data The data of the guild.
    # @param [Boolean] is_create_event Whether the guild is created by a `GUILD_CREATE` event.
    #
    def initialize(client, data, is_create_event)
      @client = client
      @data = {}
      _set_data(data, is_create_event)
    end

    def afk_channel
      @client.channels[@afk_channel_id]
    end

    def system_channel
      @client.channels[@system_channel_id]
    end

    def rules_channel
      @client.channels[@rules_channel_id]
    end

    def public_updates_channel
      @client.channels[@public_updates_channel_id]
    end

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

    def me
      @members[@client.user.id]
    end

    #
    # Leave the guild.
    # @async
    #
    # @return [Async::Task<void>] The task.
    #
    def leave
      Async do
        @client
          .http
          .request(
            Route.new(
              "/users/@me/guilds/#{@id}",
              "//users/@me/guilds/:guild_id",
              :delete
            )
          )
          .wait
        @client.guilds.delete(@id)
      end
    end

    #
    # Fetch scheduled events for the guild.
    # @async
    #
    # @param [Boolean] with_user_count Whether to include the user count in the events.
    #   Defaults to `true`.
    #
    # @return [Array<Discorb::ScheduledEvent>] The events for the guild.
    #
    def fetch_scheduled_events(with_user_count: true)
      Async do
        _resp, events =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/scheduled-events?with_user_count=#{with_user_count}",
                "//guilds/:guild_id/scheduled-events",
                :get
              )
            )
            .wait
        @scheduled_events = events.map { |e| ScheduledEvent.new(@client, e) }
      end
    end

    #
    # Fetch the scheduled event by ID.
    # @async
    #
    # @param [#to_s] id The ID of the scheduled event.
    #
    # @return [Async::Task<Discorb::ScheduledEvent>] The event with the given ID.
    # @return [Async::Task<nil>] If no event with the given ID exists.
    #
    def fetch_scheduled_event(id)
      Async do
        _resp, event =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/scheduled-events/#{id}",
                "//guilds/:guild_id/scheduled-events/:scheduled_event_id",
                :get
              )
            )
            .wait
        ScheduledEvent.new(@client, event)
      end
    end

    #
    # Create a scheduled event for the guild.
    # @async
    #
    # @param [:stage_instance, :voice, :external] type The type of event to create.
    # @param [String] name The name of the event.
    # @param [String] description The description of the event.
    # @param [Time] start_time The start time of the event.
    # @param [Time, nil] end_time The end time of the event. Defaults to `nil`.
    # @param [Discorb::Channel, Discorb::Snowflake, nil] channel The channel to run the event in.
    # @param [String, nil] location The location of the event. Defaults to `nil`.
    # @param [:guild_only] privacy_level The privacy level of the event. This must be `:guild_only`.
    #
    # @return [Async::Task<Discorb::ScheduledEvent>] The created event.
    #
    def create_scheduled_event(
      type,
      name,
      description,
      start_time,
      end_time = nil,
      privacy_level: :guild_only,
      location: nil,
      channel: nil
    )
      Async do
        payload =
          case type
          when :stage_instance
            unless channel
              raise ArgumentError,
                    "channel must be provided for stage_instance events"
            end

            {
              name:,
              description:,
              scheduled_start_time: start_time.iso8601,
              scheduled_end_time: end_time&.iso8601,
              privacy_level:
                Discorb::ScheduledEvent::PRIVACY_LEVEL.key(privacy_level),
              channel_id: channel&.id,
              entity_type:
                Discorb::ScheduledEvent::ENTITY_TYPE.key(:stage_instance)
            }
          when :voice
            unless channel
              raise ArgumentError, "channel must be provided for voice events"
            end

            {
              name:,
              description:,
              scheduled_start_time: start_time.iso8601,
              scheduled_end_time: end_time&.iso8601,
              privacy_level:
                Discorb::ScheduledEvent::PRIVACY_LEVEL.key(privacy_level),
              channel_id: channel&.id,
              entity_type: Discorb::ScheduledEvent::ENTITY_TYPE.key(:voice)
            }
          when :external
            unless location
              raise ArgumentError,
                    "location must be provided for external events"
            end
            unless end_time
              raise ArgumentError,
                    "end_time must be provided for external events"
            end

            {
              name:,
              description:,
              scheduled_start_time: start_time.iso8601,
              scheduled_end_time: end_time.iso8601,
              privacy_level:
                Discorb::ScheduledEvent::PRIVACY_LEVEL.key(privacy_level),
              entity_type: Discorb::ScheduledEvent::ENTITY_TYPE.key(:external),
              entity_metadata: {
                location:
              }
            }
          else
            raise ArgumentError, "Invalid scheduled event type: #{type}"
          end
        _resp, event =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/scheduled-events",
                "//guilds/:guild_id/scheduled-events",
                :post
              ),
              payload
            )
            .wait
        Discorb::ScheduledEvent.new(@client, event)
      end
    end

    #
    # Fetch emoji list of the guild.
    # @async
    # @note This querys the API every time. We recommend using {#emojis} instead.
    #
    # @return [Async::Task<Discorb::Dictionary{Discorb::Snowflake => Discorb::CustomEmoji}>]
    #   A dictionary of emoji in the guild.
    #
    def fetch_emoji_list
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/emojis",
                "//guilds/:guild_id/emojis",
                :get
              )
            )
            .wait
        @emojis = Dictionary.new
        ids = @emojis.map(&:id).map(&:to_s)
        data.map do |e|
          next if ids.include?(e[:id])

          @emojis[e[:id]] = CustomEmoji.new(@client, self, e)
        end
        @emojis
      end
    end

    alias fetch_emojis fetch_emoji_list

    #
    # Fetch emoji id of the guild.
    # @async
    # @note This querys the API every time. We recommend using {#emojis} instead.
    #
    # @param [#to_s] id The emoji id.
    #
    # @return [Async::Task<Discorb::CustomEmoji>] The emoji with the given id.
    #
    def fetch_emoji(id)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/emojis/#{id}",
                "//guilds/:guild_id/emojis/:emoji_id",
                :get
              )
            )
            .wait
        @emojis[e[:id]] = CustomEmoji.new(@client, self, data)
      end
    end

    #
    # Create a custom emoji.
    # @async
    #
    # @param [#to_s] name The name of the emoji.
    # @param [Discorb::Image] image The image of the emoji.
    # @param [Array<Discorb::Role>] roles A list of roles to give the emoji.
    #
    # @return [Async::Task<Discorb::CustomEmoji>] The created emoji.
    #
    def create_emoji(name, image, roles: [])
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/emojis",
                "//guilds/:guild_id/emojis",
                :post
              ),
              {
                name:,
                image: image.to_s,
                roles: roles.map { |r| Discorb::Utils.try(r, :id) }
              }
            )
            .wait
        @emojis[data[:id]] = CustomEmoji.new(@client, self, data)
      end
    end

    #
    # Fetch webhooks of the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::Webhook>>] A list of webhooks in the guild.
    #
    def fetch_webhooks
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/webhooks",
                "//guilds/:guild_id/webhooks",
                :get
              )
            )
            .wait
        data.map { |webhook| Webhook.from_data(@client, webhook) }
      end
    end

    #
    # Fetch audit log of the guild.
    # @async
    #
    # @return [Async::Task<Discorb::AuditLog>] The audit log of the guild.
    #
    def fetch_audit_log
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/audit-logs",
                "//guilds/:guild_id/audit-logs",
                :get
              )
            )
            .wait
        AuditLog.new(@client, data, self)
      end
    end

    #
    # Fetch channels of the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::Channel>>] A list of channels in the guild.
    #
    def fetch_channels
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :get
              )
            )
            .wait
        data.map { |c| Channel.make_channel(@client, c) }
      end
    end

    #
    # Create a new text channel.
    # @async
    #
    # @param [String] name The name of the channel.
    # @param [String] topic The topic of the channel.
    # @param [Integer] rate_limit_per_user The rate limit per user in the channel.
    # @param [Integer] slowmode Alias for `rate_limit_per_user`.
    # @param [Integer] position The position of the channel.
    # @param [Boolean] nsfw Whether the channel is nsfw.
    # @param [Hash{Discorb::Role, Discorb::Member => Discorb::PermissionOverwrite}] permission_overwrites
    #   A list of permission overwrites.
    # @param [Discorb::CategoryChannel] parent The parent of the channel.
    # @param [String] reason The reason for creating the channel.
    #
    # @return [Async::Task<Discorb::TextChannel>] The created text channel.
    #
    def create_text_channel(
      name,
      topic: nil,
      rate_limit_per_user: nil,
      slowmode: nil,
      position: nil,
      nsfw: nil,
      permission_overwrites: nil,
      parent: nil,
      reason: nil
    )
      Async do
        payload = { type: TextChannel.channel_type }
        payload[:name] = name
        payload[:topic] = topic if topic
        rate_limit_per_user ||= slowmode
        payload[
          :rate_limit_per_user
        ] = rate_limit_per_user if rate_limit_per_user
        payload[:nsfw] = nsfw if nsfw
        payload[:position] = position if position
        if permission_overwrites
          payload[
            :permission_overwrites
          ] = permission_overwrites.map do |target, overwrite|
            {
              type: target.is_a?(Role) ? 0 : 1,
              id: target.id,
              allow: overwrite.allow_value,
              deny: overwrite.deny_value
            }
          end
        end
        payload[:parent_id] = parent.id if parent
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        payload[:parent_id] = parent&.id
        Channel.make_channel(@client, data)
      end
    end

    #
    # Create a new voice channel.
    # @async
    #
    # @param [String] name The name of the channel.
    # @param [Integer] bitrate The bitrate of the channel.
    # @param [Integer] user_limit The user limit of the channel.
    # @param [Integer] position The position of the channel.
    # @param [Hash{Discorb::Role, Discorb::Member => Discorb::PermissionOverwrite}] permission_overwrites
    #   A list of permission overwrites.
    # @param [Discorb::CategoryChannel] parent The parent of the channel.
    # @param [String] reason The reason for creating the channel.
    #
    # @return [Async::Task<Discorb::VoiceChannel>] The created voice channel.
    #
    def create_voice_channel(
      name,
      bitrate: 64,
      user_limit: nil,
      position: nil,
      permission_overwrites: nil,
      parent: nil,
      reason: nil
    )
      Async do
        payload = { type: VoiceChannel.channel_type }
        payload[:name] = name
        payload[:bitrate] = bitrate * 1000 if bitrate
        payload[:user_limit] = user_limit if user_limit
        payload[:position] = position if position
        if permission_overwrites
          payload[
            :permission_overwrites
          ] = permission_overwrites.map do |target, overwrite|
            {
              type: target.is_a?(Role) ? 0 : 1,
              id: target.id,
              allow: overwrite.allow_value,
              deny: overwrite.deny_value
            }
          end
        end
        payload[:parent_id] = parent.id if parent
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        payload[:parent_id] = parent&.id
        Channel.make_channel(@client, data)
      end
    end

    # Create a new category channel.
    # @async
    #
    # @param [String] name The name of the channel.
    # @param [Integer] position The position of the channel.
    # @param [Hash{Discorb::Role, Discorb::Member => Discorb::PermissionOverwrite}] permission_overwrites
    #  A list of permission overwrites.
    # @param [Discorb::CategoryChannel] parent The parent of the channel.
    # @param [String] reason The reason for creating the channel.
    #
    # @return [Async::Task<Discorb::CategoryChannel>] The created category channel.
    #
    def create_category_channel(
      name,
      position: nil,
      permission_overwrites: nil,
      parent: nil,
      reason: nil
    )
      Async do
        payload = { type: CategoryChannel.channel_type }
        payload[:name] = name
        payload[:position] = position if position
        if permission_overwrites
          payload[
            :permission_overwrites
          ] = permission_overwrites.map do |target, overwrite|
            {
              type: target.is_a?(Role) ? 0 : 1,
              id: target.id,
              allow: overwrite.allow_value,
              deny: overwrite.deny_value
            }
          end
        end
        payload[:parent_id] = parent&.id
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        Channel.make_channel(@client, data)
      end
    end

    alias create_category create_category_channel

    #
    # Create a new stage channel.
    # @async
    #
    # @param [String] name The name of the channel.
    # @param [Integer] bitrate The bitrate of the channel.
    # @param [Integer] position The position of the channel.
    # @param [Hash{Discorb::Role, Discorb::Member => Discorb::PermissionOverwrite}] permission_overwrites
    #   A list of permission overwrites.
    # @param [Discorb::CategoryChannel] parent The parent of the channel.
    # @param [String] reason The reason for creating the channel.
    #
    # @return [Async::Task<Discorb::StageChannel>] The created stage channel.
    #
    def create_stage_channel(
      name,
      bitrate: 64,
      position: nil,
      permission_overwrites: nil,
      parent: nil,
      reason: nil
    )
      Async do
        payload = { type: StageChannel.channel_type }
        payload[:name] = name
        payload[:bitrate] = bitrate * 1000 if bitrate
        payload[:position] = position if position
        if permission_overwrites
          payload[
            :permission_overwrites
          ] = permission_overwrites.map do |target, overwrite|
            {
              type: target.is_a?(Role) ? 0 : 1,
              id: target.id,
              allow: overwrite.allow_value,
              deny: overwrite.deny_value
            }
          end
        end
        payload[:parent_id] = parent&.id
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        Channel.make_channel(@client, data)
      end
    end

    #
    # Create a new news channel.
    # @async
    #
    # @param [String] name The name of the channel.
    # @param [String] topic The topic of the channel.
    # @param [Integer] rate_limit_per_user The rate limit per user in the channel.
    # @param [Integer] slowmode Alias for `rate_limit_per_user`.
    # @param [Integer] position The position of the channel.
    # @param [Boolean] nsfw Whether the channel is nsfw.
    # @param [Hash{Discorb::Role, Discorb::Member => Discorb::PermissionOverwrite}] permission_overwrites
    #   A list of permission overwrites.
    # @param [Discorb::CategoryChannel] parent The parent of the channel.
    # @param [String] reason The reason for creating the channel.
    #
    # @return [Async::Task<Discorb::NewsChannel>] The created news channel.
    #
    def create_news_channel(
      name,
      topic: nil,
      rate_limit_per_user: nil,
      slowmode: nil,
      position: nil,
      nsfw: nil,
      permission_overwrites: nil,
      parent: nil,
      reason: nil
    )
      Async do
        payload = { type: NewsChannel.channel_type }
        payload[:name] = name
        payload[:topic] = topic if topic
        rate_limit_per_user ||= slowmode
        payload[
          :rate_limit_per_user
        ] = rate_limit_per_user if rate_limit_per_user
        payload[:position] = position if position
        if permission_overwrites
          payload[
            :permission_overwrites
          ] = permission_overwrites.map do |target, overwrite|
            {
              type: target.is_a?(Role) ? 0 : 1,
              id: target.id,
              allow: overwrite.allow_value,
              deny: overwrite.deny_value
            }
          end
        end
        payload[:nsfw] = nsfw unless nsfw.nil?
        payload[:parent_id] = parent&.id
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/channels",
                "//guilds/:guild_id/channels",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        Channel.make_channel(@client, data)
      end
    end

    #
    # Fetch a list of active threads in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::ThreadChannel>>] The list of threads.
    #
    def fetch_active_threads
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/threads/active",
                "//guilds/:guild_id/threads/active",
                :get
              )
            )
            .wait
        data[:threads].map { |t| Channel.make_thread(@client, t) }
      end
    end

    #
    # Fetch a member in the guild.
    # @async
    #
    # @param [#to_s] id The ID of the member to fetch.
    #
    # @return [Async::Task<Discorb::Member>] The member.
    # @return [Async::Task<nil>] If the member is not found.
    #
    def fetch_member(id)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/members/#{id}",
                "//guilds/:guild_id/members/:user_id",
                :get
              )
            )
            .wait
      rescue Discorb::NotFoundError
        nil
      else
        Member.new(@client, @id, data[:user], data)
      end
    end

    # Fetch members in the guild.
    # @async
    # @macro members_intent
    #
    # @param [Integer] limit The maximum number of members to fetch, 0 for all.
    # @param [Integer] after The ID of the member to start fetching after.
    #
    # @return [Async::Task<Array<Discorb::Member>>] The list of members.
    #
    def fetch_members(limit: 0, after: nil)
      Async do
        unless limit.zero?
          _resp, data =
            @client
              .http
              .request(
                Route.new(
                  "/guilds/#{@id}/members?#{
                    URI.encode_www_form({ after:, limit: })
                  }",
                  "//guilds/:guild_id/members",
                  :get
                )
              )
              .wait
          next data[:members].map { |m| Member.new(@client, @id, m[:user], m) }
        end
        ret = []
        after = 0
        loop do
          params = { after:, limit: 100 }
          _resp, data =
            @client
              .http
              .request(
                Route.new(
                  "/guilds/#{@id}/members?#{URI.encode_www_form(params)}",
                  "//guilds/:guild_id/members",
                  :get
                )
              )
              .wait
          ret += data.map { |m| Member.new(@client, @id, m[:user], m) }
          after = data.last[:user][:id]
          break if data.length != 100
        end
        ret
      end
    end

    alias fetch_member_list fetch_members

    #
    # Search for members by name in the guild.
    # @async
    #
    # @param [String] name The name of the member to search for.
    # @param [Integer] limit The maximum number of members to return.
    #
    # @return [Async::Task<Array<Discorb::Member>>] The list of members.
    #
    def fetch_members_named(name, limit: 1)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/members/search?#{
                  URI.encode_www_form({ query: name, limit: })
                }",
                "//guilds/:guild_id/members/search",
                :get
              )
            )
            .wait
        data.map { |d| Member.new(@client, @id, d[:user], d) }
      end
    end

    #
    # Almost the same as {#fetch_members_named}, but returns a single member.
    # @async
    #
    # @return [Async::Task<Discorb::Member>] The member.
    # @return [Async::Task<nil>] If the member is not found.
    #
    def fetch_member_named(...)
      Async { fetch_members_named(...).first }
    end

    #
    # Change nickname of client member.
    # @async
    #
    # @param [String] nickname The nickname to set.
    # @param [String] reason The reason for changing the nickname.
    #
    # @return [Async::Task<void>] The task.
    #
    def edit_nickname(nickname, reason: nil)
      Async do
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@id}/members/@me/nick",
              "//guilds/:guild_id/members/@me/nick",
              :patch
            ),
            { nick: nickname },
            audit_log_reason: reason
          )
          .wait
      end
    end

    alias edit_nick edit_nickname
    alias modify_nickname edit_nickname
    alias modify_nick modify_nickname

    #
    # Kick a member from the guild.
    # @async
    #
    # @param [Discorb::Member] member The member to kick.
    # @param [String] reason The reason for kicking the member.
    #
    # @return [Async::Task<void>] The task.
    #
    def kick_member(member, reason: nil)
      Async do
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@id}/members/#{member.id}",
              "//guilds/:guild_id/members/:user_id",
              :delete
            ),
            {},
            audit_log_reason: reason
          )
          .wait
      end
    end

    #
    # Fetch a list of bans in the guild.
    # @async
    #
    # @param [Integer] limit The number of bans to fetch.
    # @param [Discorb::Snowflake] before The ID of the ban to fetch before.
    # @param [Discorb::Snowflake] after The ID of the ban to fetch after.
    # @param [Discorb::Snowflake] around The ID of the ban to fetch around.
    #
    # @return [Async::Task<Array<Discorb::Guild::Ban>>] The list of bans.
    #
    def fetch_bans(limit = 50, before: nil, after: nil, around: nil)
      Async do
        params =
          {
            limit:,
            before: Discorb::Utils.try(after, :id),
            after: Discorb::Utils.try(around, :id),
            around: Discorb::Utils.try(before, :id)
          }.filter { |_k, v| !v.nil? }.to_h
        _resp, bans =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/bans?#{URI.encode_www_form(params)}",
                "//guilds/:guild_id/bans",
                :get
              )
            )
            .wait
        bans.map { |d| Ban.new(@client, self, d) }
      end
    end

    #
    # Fetch a ban in the guild.
    # @async
    #
    # @param [Discorb::User] user The user to fetch.
    #
    # @return [Async::Task<Discorb::Guild::Ban>] The ban.
    # @return [Async::Task<nil>] If the ban is not found.
    #
    def fetch_ban(user)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/bans/#{user.id}",
                "//guilds/:guild_id/bans/:user_id",
                :get
              )
            )
            .wait
      rescue Discorb::NotFoundError
        nil
      else
        Ban.new(@client, self, data)
      end
    end

    #
    # Checks the user was banned from the guild.
    # @async
    #
    # @param [Discorb::User] user The user to check.
    #
    # @return [Async::Task<Boolean>] Whether the user was banned.
    #
    def banned?(user)
      Async { !fetch_ban(user).wait.nil? }
    end

    #
    # Ban a member from the guild.
    # @async
    #
    # @param [Discorb::Member] member The member to ban.
    # @param [Integer] delete_message_days The number of days to delete messages.
    # @param [String] reason The reason for banning the member.
    #
    # @return [Async::Task<Discorb::Guild::Ban>] The ban.
    #
    def ban_member(member, delete_message_days: 0, reason: nil)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/bans",
                "//guilds/:guild_id/bans",
                :post
              ),
              { user: member.id, delete_message_days: },
              audit_log_reason: reason
            )
            .wait
        Ban.new(@client, self, data)
      end
    end

    #
    # Unban a user from the guild.
    # @async
    #
    # @param [Discorb::User] user The user to unban.
    # @param [String] reason The reason for unbanning the user.
    #
    # @return [Async::Task<void>] The task.
    #
    def unban_user(user, reason: nil)
      Async do
        @client
          .http
          .request(
            Route.new(
              "/guilds/#{@id}/bans/#{user.id}",
              "//guilds/:guild_id/bans/:user_id",
              :delete
            ),
            {},
            audit_log_reason: reason
          )
          .wait
      end
    end

    #
    # Fetch a list of roles in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::Role>>] The list of roles.
    #
    def fetch_roles
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/roles",
                "//guilds/:guild_id/roles",
                :get
              )
            )
            .wait
        data.map { |d| Role.new(@client, self, d) }
      end
    end

    #
    # Create a role in the guild.
    # @async
    #
    # @param [String] name The name of the role.
    # @param [Discorb::Color] color The color of the role.
    # @param [Boolean] hoist Whether the role should be hoisted.
    # @param [Boolean] mentionable Whether the role should be mentionable.
    # @param [String] reason The reason for creating the role.
    #
    # @return [Async::Task<Discorb::Role>] The role.
    #
    def create_role(
      name = nil,
      color: nil,
      hoist: nil,
      mentionable: nil,
      reason: nil
    )
      Async do
        payload = {}
        payload[:name] = name if name
        payload[:color] = color.to_i if color
        payload[:hoist] = hoist if hoist
        payload[:mentionable] = mentionable if mentionable
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/roles",
                "//guilds/:guild_id/roles",
                :post
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        Role.new(@client, self, data)
      end
    end

    #
    # Fetch how many members will be pruned.
    # @async
    #
    # @param [Integer] days The number of days to prune.
    # @param [Array<Discorb::Role>] roles The roles that include for pruning.
    #
    # @return [Async::Task<Integer>] The number of members that will be pruned.
    #
    def fetch_prune(days = 7, roles: [])
      Async do
        params = { days:, include_roles: @id.to_s }
        param[:include_roles] = roles
          .map(&:id)
          .map(&:to_s)
          .join(";") if roles.any?
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/prune?#{URI.encode_www_form(params)}",
                "//guilds/:guild_id/prune",
                :get
              )
            )
            .wait
        data[:pruned]
      end
    end

    #
    # Prune members from the guild.
    # @async
    #
    # @param [Integer] days The number of days to prune.
    # @param [Array<Discorb::Role>] roles The roles that include for pruning.
    # @param [String] reason The reason for pruning.
    #
    # @return [Async::Task<Integer>] The number of members that were pruned.
    #
    def prune(days = 7, roles: [], reason: nil)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/prune",
                "//guilds/:guild_id/prune",
                :post
              ),
              { days:, roles: roles.map(&:id) },
              audit_log_reason: reason
            )
            .wait
        data[:pruned]
      end
    end

    #
    # Fetch voice regions that are available in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::VoiceRegion>>] The available voice regions.
    #
    def fetch_voice_regions
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/voice",
                "//guilds/:guild_id/voice",
                :get
              )
            )
            .wait
        data.map { |d| VoiceRegion.new(d) }
      end
    end

    #
    # Fetch invites in the guild.
    # @async
    #
    # @return [Async::Task<Array<Invite>>] The invites.
    #
    def fetch_invites
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/invites",
                "//guilds/:guild_id/invites",
                :get
              )
            )
            .wait
        data.map { |d| Invite.new(@client, d, false) }
      end
    end

    #
    # Fetch integrations in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::Integration>>] The integrations.
    #
    def fetch_integrations
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/integrations",
                "//guilds/:guild_id/integrations",
                :get
              )
            )
            .wait
        data.map { |d| Integration.new(@client, d, @id) }
      end
    end

    #
    # Fetch the widget of the guild.
    # @async
    #
    # @return [Async::Task<Discorb::Guild::Widget>] The widget.
    #
    def fetch_widget
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/widget",
                "//guilds/:guild_id/widget",
                :get
              )
            )
            .wait
        Widget.new(@client, @id, data)
      end
    end

    #
    # Fetch the vanity URL of the guild.
    # @async
    #
    # @return [Async::Task<Discorb::Guild::VanityInvite>] The vanity URL.
    #
    def fetch_vanity_invite
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/vanity-url",
                "//guilds/:guild_id/vanity-url",
                :get
              )
            )
            .wait
        VanityInvite.new(@client, self, data)
      end
    end

    #
    # Fetch the welcome screen of the guild.
    # @async
    #
    # @return [Async::Task<Discorb::WelcomeScreen>] The welcome screen.
    #
    def fetch_welcome_screen
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/welcome-screen",
                "//guilds/:guild_id/welcome-screen",
                :get
              )
            )
            .wait
        WelcomeScreen.new(@client, self, data)
      end
    end

    #
    # Fetch stickers in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::Sticker::GuildSticker>>] The stickers.
    #
    def fetch_stickers
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/stickers",
                "//guilds/:guild_id/stickers",
                :get
              )
            )
            .wait
        data.map { |d| Sticker::GuildSticker.new(@client, d) }
      end
    end

    #
    # Fetch the sticker by ID.
    # @async
    #
    # @param [#to_s] id The ID of the sticker.
    #
    # @return [Async::Task<Discorb::Sticker::GuildSticker>] The sticker.
    # @return [Async::Task<nil>] If the sticker does not exist.
    #
    def fetch_sticker(id)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/stickers/#{id}",
                "//guilds/:guild_id/stickers/:sticker_id",
                :get
              )
            )
            .wait
      rescue Discorb::NotFoundError
        nil
      else
        Sticker::GuildSticker.new(@client, data)
      end
    end

    #
    # Fetch templates in the guild.
    # @async
    #
    # @return [Async::Task<Discorb::GuildTemplate>] The templates.
    #
    def fetch_templates
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/templates",
                "//guilds/:guild_id/templates",
                :get
              )
            )
            .wait
        data.map { |d| GuildTemplate.new(@client, d) }
      end
    end

    #
    # Almost the same as {#fetch_templates}, but returns a single template.
    #
    # @return [Discorb::GuildTemplate] The template.
    # @return [Async::Task<nil>] If the template does not exist.
    #
    def fetch_template
      Async { fetch_templates.wait.first }
    end

    #
    # Create a new template in the guild.
    #
    # @param [String] name The name of the template.
    # @param [String] description The description of the template.
    # @param [String] reason The reason for creating the template.
    #
    # @return [Async::Task<Discorb::GuildTemplate>] The template.
    #
    def create_template(name, description = nil, reason: nil)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/templates",
                "//guilds/:guild_id/templates",
                :post
              ),
              { name:, description: },
              audit_log_reason: reason
            )
            .wait
        GuildTemplate.new(@client, data)
      end
    end

    #
    # Fetch the automod rules in the guild.
    # @async
    #
    # @return [Async::Task<Array<Discorb::AutoModRule>>] The automod rules.
    #
    def fetch_automod_rules
      Async do
        _resp, data =
          @client.http.request(
            Route.new(
              "/guilds/#{@id}/auto-moderation/rules",
              "//guilds/:guild_id/auto-moderation/rules",
              :get
            )
          )
        data.map { |d| AutoModRule.new(@client, d) }
      end
    end

    alias fetch_automod_rule_list fetch_automod_rules

    #
    # Fetch the automod rule by ID.
    #
    # @param [#to_s] id The ID of the automod rule.
    #
    # @return [Async::Task<Array<Discord::AutoModRule>>] The automod rule.
    #
    def fetch_automod_rule(id)
      Async do
        _resp, data =
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@id}/auto-moderation/rules/#{id}",
                "//guilds/:guild_id/auto-moderation/rules/:rule_id",
                :get
              )
            )
            .wait
        AutoModRule.new(@client, data)
      end
    end

    #
    # Create a new automod rule in the guild.
    # @async
    #
    # @param [String] name The name of the rule.
    # @param [Symbol] trigger_type The trigger type of the rule. See {Discorb::AutoModRule::TRIGGER_TYPES}.
    # @param [Array<Discorb::AutoModRule::Action>] actions The actions of the rule.
    # @param [Symbol] event_type The event type of the rule. See {Discorb::AutoModRule::EVENT_TYPES}.
    # @param [Boolean] enabled Whether the rule is enabled or not.
    # @param [Array<Discorb::Role>] exempt_roles The roles that are exempt from the rule.
    # @param [Array<Discorb::Channel>] exempt_channels The channels that are exempt from the rule.
    # @param [Array<String>] keyword_filter The keywords to filter.
    # @param [Array<String>] allow_list Substrings which will be exempt from triggering the preset trigger type.
    # @param [Integer] mention_total_limit The total number of mentions allowed per message.
    # @param [Symbol] presets The preset of the rule. See {Discorb::AutoModRule::PRESET_TYPES}.
    # @param [String] reason The reason for creating the rule.
    #
    # @return [Async::Task<Discorb::AutoModRule>] The automod rule.
    #
    def create_automod_rule(
      name,
      trigger_type,
      actions,
      event_type = :message_send,
      enabled: false,
      exempt_roles: [],
      exempt_channels: [],
      keyword_filter: nil,
      mention_total_limit: nil,
      allow_list: nil,
      presets: nil,
      reason: nil
    )
      Async do
        payload = {
          name:,
          event_type: Discorb::AutoModRule::EVENT_TYPES.key(event_type),
          trigger_type: Discorb::AutoModRule::TRIGGER_TYPES.key(trigger_type),
          metadata: {
            keyword_filter:,
            presets: presets && Discorb::AutoModRule::PRESET_TYPES.key(presets),
            allow_list:,
            mention_total_limit:
          },
          actions: actions.map(&:to_hash),
          enabled:,
          exempt_roles: exempt_roles.map(&:id),
          exempt_channels: exempt_channels.map(&:id)
        }

        _resp, data =
          @client.http.request(
            Route.new(
              "/guilds/#{@id}/auto-moderation/rules",
              "//guilds/:guild_id/auto-moderation/rules",
              :post
            ),
            payload,
            audit_log_reason: reason
          )
        Discorb::AutoModRule.new(@client, data)
      end
    end

    #
    # Represents a vanity invite.
    #
    class VanityInvite < DiscordModel
      # @return [String] The vanity invite code.
      attr_reader :code
      # @return [Integer] The number of uses.
      attr_reader :uses

      # @!attribute [r] url
      #   @return [String] The vanity URL.

      #
      # Initialize a new instance of the {VanityInvite} class.
      # @private
      #
      # @param [Discorb::Client] client The client.
      # @param [Discorb::Guild] guild The guild.
      # @param [Hash] data The data of the invite.
      #
      def initialize(client, guild, data)
        @client = client
        @guild = guild
        @code = data[:code]
        @uses = data[:uses]
      end

      def url
        "https://discord.gg/#{@code}"
      end
    end

    #
    # Represents a guild widget.
    #
    class Widget < DiscordModel
      # @return [Discorb::Snowflake] The guild ID.
      attr_reader :guild_id
      # @return [Discorb::Snowflake] The channel ID.
      attr_reader :channel_id
      # @return [Boolean] Whether the widget is enabled.
      attr_reader :enabled
      alias enabled? enabled
      alias enable? enabled

      # @!attribute [r] channel
      #   @macro client_cache
      #   @return [Discorb::Channel] The channel.
      # @!attribute [r] guild
      #   @macro client_cache
      #   @return [Discorb::Guild] The guild.
      # @!attribute [r] json_url
      #   @return [String] The JSON URL.

      #
      # Initialize a new instance of the {Widget} class.
      # @private
      #
      # @param [Discorb::Client] client The client.
      # @param [Discorb::Snowflake] guild_id The guild ID.
      # @param [Hash] data The data from Discord.
      #
      def initialize(client, guild_id, data)
        @client = client
        @enabled = data[:enabled]
        @guild_id = Snowflake.new(guild_id)
        @channel_id = Snowflake.new(data[:channel_id])
      end

      def channel
        @client.channels[@channel_id]
      end

      #
      # Edit the widget.
      # @async
      # @macro edit
      #
      # @param [Boolean] enabled Whether the widget is enabled.
      # @param [Discorb::GuildChannel] channel The channel.
      # @param [String] reason The reason for editing the widget.
      #
      # @return [Async::Task<void>] The task.
      #
      def edit(enabled: nil, channel: nil, reason: nil)
        Async do
          payload = {}
          payload[:enabled] = enabled unless enabled.nil?
          payload[:channel_id] = channel.id if channel_id
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@guild_id}/widget",
                "//guilds/:guild_id/widget",
                :patch
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        end
      end

      alias modify edit

      def json_url
        "#{Discorb::API_BASE_URL}/guilds/#{@guild_id}/widget.json"
      end

      #
      # Return iframe HTML of the widget.
      #
      # @param ["dark", "light"] theme The theme of the widget.
      # @param [Integer] width The width of the widget.
      # @param [Integer] height The height of the widget.
      #
      # @return [String] The iframe HTML.
      #
      def iframe(theme: "dark", width: 350, height: 500)
        # rubocop:disable Layout/LineLength
        [
          %(<iframe src="https://canary.discord.com/widget?id=#{@guild_id}&theme=#{theme}" width="#{width}" height="#{height}"),
          %(allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>)
        ].join
        # rubocop:enable Layout/LineLength
      end
    end

    #
    # Represents a ban.
    #
    class Ban < DiscordModel
      # @return [Discorb::User] The user.
      attr_reader :user
      # @return [String] The reason for the ban.
      attr_reader :reason

      #
      # Initialize a new instance of the {Ban} class.
      # @private
      #
      # @param [Discorb::Client] client The client.
      # @param [Discorb::Guild] guild The guild.
      # @param [Hash] data The data from Discord.
      #
      def initialize(client, guild, data)
        @client = client
        @guild = guild
        @reason = data[:reason]
        @user =
          @client.users[data[:user][:id]] || User.new(@client, data[:user])
      end

      def inspect
        "<#{self.class.name} #{@user}>"
      end
    end

    class << self
      #
      # Returns a banner url from the guild's ID.
      #
      # @param [#to_s] guild_id The ID of the guild.
      # @param [:shield, :banner1, :banner2, :banner3, :banner4] style The style of the banner.
      #
      # @return [String] The url of the banner.
      #
      def banner(guild_id, style: :banner)
        "#{Discorb::API_BASE_URL}/guilds/#{guild_id}/widget.png&style=#{style}"
      end
    end

    private

    def _set_data(data, is_create_event)
      @id = Snowflake.new(data[:id])
      if data[:unavailable]
        @unavailable = true
        return
      end
      @client.guilds[@id] = self unless data[:no_cache]
      @icon = data[:icon] && Asset.new(self, data[:icon])
      @unavailable = false
      @name = data[:name]
      @members = Discorb::Dictionary.new
      data[:members]&.each { |m| Member.new(@client, @id, m[:user], m) }
      @splash =
        data[:splash] && Asset.new(self, data[:splash], path: "splashes/#{@id}")
      @discovery_splash =
        data[:discovery_splash] &&
          Asset.new(
            self,
            data[:discovery_splash],
            path: "discovery-splashes/#{@id}"
          )
      @owner_id = data[:owner_id]
      @permissions = Permission.new(data[:permissions].to_i)
      @afk_channel_id = data[:afk_channel_id]
      @afk_timeout = data[:afk_timeout]
      @widget_enabled = data[:widget_enabled]
      @widget_channel_id = data[:widget_channel_id]
      @roles = Dictionary.new
      data[:roles].each { |r| @roles[r[:id]] = Role.new(@client, self, r) }
      @emojis = Dictionary.new
      data[:emojis].map do |e|
        @emojis[e[:id]] = CustomEmoji.new(@client, self, e)
      end
      @features = data[:features].map { |f| f.downcase.to_sym }
      @mfa_level = MFA_LEVELS[data[:mfa_level]]
      @verification_level = VERIFICATION_LEVELS[data[:verification_level]]
      @default_message_notifications =
        DEFAULT_MESSAGE_NOTIFICATIONS[data[:default_message_notifications]]
      @explicit_content_filter =
        EXPLICIT_CONTENT_FILTER[data[:explicit_content_filter]]
      @system_channel_id = data[:system_channel_id]
      @system_channel_flag =
        SystemChannelFlag.new(0b111 - data[:system_channel_flags])
      @rules_channel_id = data[:rules_channel_id]
      @vanity_url_code = data[:vanity_url_code]
      @description = data[:description]
      @banner =
        data[:banner] && Asset.new(self, data[:banner], path: "banners/#{@id}")
      @premium_tier = data[:premium_tier]
      @premium_subscription_count = data[:premium_tier_count].to_i
      @preferred_locale = data[:preferred_locale].gsub("-", "_").to_sym
      @public_updates_channel_id = data[:public_updates_channel_id]
      @max_video_channel_users = data[:max_video_channel_users]
      @approximate_member_count = data[:approximate_member_count]
      @approximate_presence_count = data[:approximate_presence_count]
      @welcome_screen =
        (
          if data[:welcome_screen].nil?
            nil
          else
            WelcomeScreen.new(@client, self, data[:welcome_screen])
          end
        )
      @nsfw_level = NSFW_LEVELS[data[:nsfw_level]]
      return unless is_create_event

      @stickers =
        (
          if data[:stickers].nil?
            []
          else
            data[:stickers].map { |s| Sticker::GuildSticker.new(@client, s) }
          end
        )
      @joined_at = Time.iso8601(data[:joined_at])
      @large = data[:large]
      @member_count = data[:member_count]
      tmp_channels =
        data[:channels]
          .filter { |c| !c.key?(:thread_metadata) }
          .map { |c| Channel.make_channel(@client, c.merge({ guild_id: @id })) }
      @channels =
        Dictionary.new(
          tmp_channels.to_h { |c| [c.id, c] },
          sort: ->(c) { c[1].position }
        )
      @voice_states =
        Dictionary.new(
          data[:voice_states].to_h do |v|
            [
              Snowflake.new(v[:user_id]),
              VoiceState.new(@client, v.merge({ guild_id: @id }))
            ]
          end
        )
      @threads =
        (
          if data[:threads]
            data[:threads].map { |t| Channel.make_channel(@client, t) }
          else
            []
          end
        )
      @presences =
        Dictionary.new(
          data[:presences].to_h do |pr|
            [Snowflake.new(pr[:user][:id]), Presence.new(@client, pr)]
          end
        )
      @max_presences = data[:max_presences]
      @stage_instances =
        Dictionary.new(
          data[:stage_instances].to_h do |s|
            [Snowflake.new(s[:id]), StageInstance.new(@client, s)]
          end
        )
      @scheduled_events =
        Dictionary.new(
          data[:guild_scheduled_events].to_h do |s|
            [Snowflake.new(s[:id]), ScheduledEvent.new(@client, s)]
          end
        )
      @data.update(data)
    end
  end

  #
  # Represents a system channel flag.
  # ## Flag fields
  # |Field|Value|
  # |-|-|
  # |`1 << 0`|`:member_join`|
  # |`1 << 1`|`:server_boost`|
  # |`1 << 2`|`:setup_tips`|
  # |`1 << 3`|`:join_stickers`|
  #
  class SystemChannelFlag < Flag
    @bits = {
      member_join: 0,
      server_boost: 1,
      setup_tips: 2,
      join_stickers: 3
    }.freeze
  end

  #
  # Represents a welcome screen.
  #
  class WelcomeScreen < DiscordModel
    # @return [String] The description of the welcome screen.
    attr_reader :description
    # @return [Array<Discorb::WelcomeScreen::Channel>] The channels to display the welcome screen.
    attr_reader :channels
    # @return [Discorb::Guild] The guild the welcome screen belongs to.
    attr_reader :guild

    #
    # Initializes the welcome screen.
    # @private
    #
    # @param [Discorb::Client] client The client.
    # @param [Discorb::Guild] guild The guild the welcome screen belongs to.
    # @param [Hash] data The data of the welcome screen.
    #
    def initialize(client, guild, data)
      @client = client
      @description = data[:description]
      @guild = guild
      @channels =
        data[:channels].map do |c|
          WelcomeScreen::Channel.new(
            client.channels[c[:channel_id]],
            c,
            c[:emoji_name] &&
              if c[:emoji_id]
                (
                  client.emojis[c[:emoji_id]] ||
                    Discorb::PartialEmoji.new(
                      { name: c[:emoji_name], id: c[:emoji_id] }
                    )
                )
              else
                Discorb::UnicodeEmoji.new(c[:emoji_name])
              end
          )
        end
    end

    #
    # Represents a channel to display the welcome screen.
    #
    class Channel < DiscordModel
      # @return [String] The channel's name.
      attr_reader :description

      # @!attribute [r] emoji
      #   @return [Discorb::Emoji] The emoji to display.
      # @!attribute [r] channel
      #   @macro client_cache
      #   @return [Discorb::Channel] The channel to display the welcome screen.

      #
      # Initialize a new welcome screen channel.
      #
      # @param [Discorb::TextChannel] channel The channel to display the welcome screen.
      # @param [String] description The channel's name.
      # @param [Discorb::Emoji] emoji The emoji to display.
      #
      def initialize(channel, description, emoji)
        if description.is_a?(Hash)
          @screen = channel
          data = description
          @channel_id = Snowflake.new(data[:channel_id])
          @description = data[:description]
          @emoji_id = Snowflake.new(data[:emoji_id])
          @emoji_name = data[:emoji_name]
        else
          @channel_id = channel.id
          @description = description
          if emoji.is_a?(UnicodeEmoji)
            @emoji_id = nil
            @emoji_name = emoji.value
          else
            @emoji_id = emoji.id
            @emoji_name = emoji.name
          end
        end
      end

      #
      # Converts the channel to a hash.
      #
      # @return [Hash] The hash.
      # @see https://discord.com/developers/docs/resources/guild#welcome-screen-object
      #
      def to_hash
        {
          channel_id: @channel_id,
          description: @description,
          emoji_id: @emoji_id,
          emoji_name: @emoji_name
        }
      end

      def channel
        @screen.guild.channels[@channel_id]
      end

      def emoji
        if @emoji_id.nil?
          UnicodeEmoji.new(@emoji_name)
        else
          @screen.guild.emojis[@emoji_id]
        end
      end

      #
      # Edits the welcome screen.
      # @async
      # @macro edit
      #
      # @param [Boolean] enabled Whether the welcome screen is enabled.
      # @param [Array<Discorb::WelcomeScreen::Channel>] channels The channels to display the welcome screen.
      # @param [String] description The description of the welcome screen.
      # @param [String] reason The reason for editing the welcome screen.
      #
      # @return [Async::Task<void>] The task.
      #
      def edit(
        enabled: Discorb::Unset,
        channels: Discorb::Unset,
        description: Discorb::Unset,
        reason: nil
      )
        Async do
          payload = {}
          payload[:enabled] = enabled unless enabled == Discorb::Unset
          payload[:welcome_channels] = channels.map(
            &:to_hash
          ) unless channels == Discorb::Unset
          payload[:description] = description unless description ==
            Discorb::Unset
          @client
            .http
            .request(
              Route.new(
                "/guilds/#{@guild.id}/welcome-screen",
                "//guilds/:guild_id/welcome-screen",
                :patch
              ),
              payload,
              audit_log_reason: reason
            )
            .wait
        end
      end
    end
  end
end