class Discordrb::Bot

Represents a Discord bot, including servers, users, etc.

Attributes

awaits[R]

@return [Hash<Symbol => Await>] the list of registered {Await}s.

event_threads[R]

The list of currently running threads used to parse and call events. The threads will have a local variable ‘:discordrb_name` in the format of `et-1234`, where “et” stands for “event thread” and the number is a continually incrementing number representing how many events were executed before. @return [Array<Thread>] The threads.

gateway[R]

The gateway connection is an internal detail that is useless to most people. It is however essential while debugging or developing discordrb itself, or while writing very custom bots. @return [Gateway] the underlying {Gateway} object.

name[RW]

The bot’s name which discordrb sends to Discord when making any request, so Discord can identify bots with the same codebase. Not required but I recommend setting it anyway. @return [String] The bot’s name.

shard_key[R]

@return [Array(Integer, Integer)] the current shard key

should_parse_self[RW]

@return [true, false] whether or not the bot should parse its own messages. Off by default.

voices[R]

@return [Hash<Integer => VoiceBot>] the voice connections this bot currently has, by the server ID to which they are connected.

Public Class Methods

new( log_mode: :normal, token: nil, client_id: nil, type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false, shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false, compress_mode: :large, intents: :all ) click to toggle source

Makes a new bot with the given authentication data. It will be ready to be added event handlers to and can eventually be run with {#run}.

As support for logging in using username and password has been removed in version 3.0.0, only a token login is possible. Be sure to specify the ‘type` parameter as `:user` if you’re logging in as a user.

Simply creating a bot won’t be enough to start sending messages etc. with, only a limited set of methods can be used after logging in. If you want to do something when the bot has connected successfully, either do it in the {#ready} event, or use the {#run} method with the :async parameter and do the processing after that. @param log_mode [Symbol] The mode this bot should use for logging. See {Logger#mode=} for a list of modes. @param token [String] The token that should be used to log in. If your bot is a bot account, you have to specify

this. If you're logging in as a user, make sure to also set the account type to :user so discordrb doesn't think
you're trying to log in as a bot.

@param client_id [Integer] If you’re logging in as a bot, the bot’s client ID. This is optional, and may be fetched

from the API by calling {Bot#bot_application} (see {Application}).

@param type [Symbol] This parameter lets you manually overwrite the account type. This needs to be set when

logging in as a user, otherwise discordrb will treat you as a bot account. Valid values are `:user` and `:bot`.

@param name [String] Your bot’s name. This will be sent to Discord with any API requests, who will use this to

trace the source of excessive API requests; it's recommended to set this to something if you make bots that many
people will host on their servers separately.

@param fancy_log [true, false] Whether the output log should be made extra fancy using ANSI escape codes. (Your

terminal may not support this.)

@param suppress_ready [true, false] Whether the READY packet should be exempt from being printed to console.

Useful for very large bots running in debug or verbose log_mode.

@param parse_self [true, false] Whether the bot should react on its own messages. It’s best to turn this off

unless you really need this so you don't inadvertently create infinite loops.

@param shard_id [Integer] The number of the shard this bot should handle. See

https://github.com/discord/discord-api-docs/issues/17 for how to do sharding.

@param num_shards [Integer] The total number of shards that should be running. See

https://github.com/discord/discord-api-docs/issues/17 for how to do sharding.

@param redact_token [true, false] Whether the bot should redact the token in logs. Default is true. @param ignore_bots [true, false] Whether the bot should ignore bot accounts or not. Default is false. @param compress_mode [:none, :large, :stream] Sets which compression mode should be used when connecting

to Discord's gateway. `:none` will request that no payloads are received compressed (not recommended for
production bots). `:large` will request that large payloads are received compressed. `:stream` will request
that all data be received in a continuous compressed stream.

@param intents [:all, :unprivileged, Array<Symbol>, :none] Gateway intents that this bot requires. ‘:all` will

request all intents. `:unprivileged` will request only intents that are not defined as "Privileged". `:none`
will request no intents. An array of symbols will request only those intents specified.

@see Discordrb::INTENTS

# File lib/discordrb/bot.rb, line 114
def initialize(
  log_mode: :normal,
  token: nil, client_id: nil,
  type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
  shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
  compress_mode: :large, intents: :all
)
  LOGGER.mode = log_mode
  LOGGER.token = token if redact_token

  @should_parse_self = parse_self

  @client_id = client_id

  @type = type || :bot
  @name = name

  @shard_key = num_shards ? [shard_id, num_shards] : nil

  LOGGER.fancy = fancy_log
  @prevent_ready = suppress_ready

  @compress_mode = compress_mode

  raise 'Token string is empty or nil' if token.nil? || token.empty?

  @intents = case intents
             when :all
               ALL_INTENTS
             when :unprivileged
               UNPRIVILEGED_INTENTS
             when :none
               NO_INTENTS
             else
               calculate_intents(intents)
             end

  @token = process_token(@type, token)
  @gateway = Gateway.new(self, @token, @shard_key, @compress_mode, @intents)

  init_cache

  @voices = {}
  @should_connect_to_voice = {}

  @ignored_ids = Set.new
  @ignore_bots = ignore_bots

  @event_threads = []
  @current_thread = 0

  @status = :online

  @application_commands = {}
end

Public Instance Methods

accept_invite(invite) click to toggle source

Makes the bot join an invite to a server. @param invite [String, Invite] The invite to join. For possible formats see {#resolve_invite_code}.

# File lib/discordrb/bot.rb, line 302
def accept_invite(invite)
  resolved = invite(invite).code
  API::Invite.accept(token, resolved)
end
add_await(key, type, attributes = {}, &block) click to toggle source

Add an await the bot should listen to. For information on awaits, see {Await}. @param key [Symbol] The key that uniquely identifies the await for {AwaitEvent}s to listen to (see {#await}). @param type [Class] The event class that should be listened for. @param attributes [Hash] The attributes the event should check for. The block will only be executed if all attributes match. @yield Is executed when the await is triggered. @yieldparam event [Event] The event object that was triggered. @return [Await] The await that was created. @deprecated Will be changed to blocking behavior in v4.0. Use {#add_await!} instead.

# File lib/discordrb/bot.rb, line 684
def add_await(key, type, attributes = {}, &block)
  raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent

  await = Await.new(self, key, type, attributes, block)
  @awaits ||= {}
  @awaits[key] = await
end
add_await!(type, attributes = {}) { |event| ... } click to toggle source

Awaits an event, blocking the current thread until a response is received. @param type [Class] The event class that should be listened for. @option attributes [Numeric] :timeout the amount of time (in seconds) to wait for a response before returning ‘nil`. Waits forever if omitted. @yield Executed when a matching event is received. @yieldparam event [Event] The event object that was triggered. @yieldreturn [true, false] Whether the event matches extra await criteria described by the block @return [Event, nil] The event object that was triggered, or `nil` if a `timeout` was set and no event was raised in time. @raise [ArgumentError] if `timeout` is given and is not a positive numeric value

# File lib/discordrb/bot.rb, line 700
def add_await!(type, attributes = {})
  raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent

  timeout = attributes[:timeout]
  raise ArgumentError, 'Timeout must be a number > 0' if timeout.is_a?(Numeric) && !timeout.positive?

  mutex = Mutex.new
  cv = ConditionVariable.new
  response = nil
  block = lambda do |event|
    mutex.synchronize do
      response = event
      if block_given?
        result = yield(event)
        cv.signal if result.is_a?(TrueClass)
      else
        cv.signal
      end
    end
  end

  handler = register_event(type, attributes, block)

  if timeout
    Thread.new do
      sleep timeout
      mutex.synchronize { cv.signal }
    end
  end

  mutex.synchronize { cv.wait(mutex) }

  remove_handler(handler)
  raise 'ConditionVariable was signaled without returning an event!' if response.nil? && timeout.nil?

  response
end
add_thread_member(channel, member) click to toggle source

Add a member to a thread @param channel [Channel, Integer, String] @param member [Member, Integer, String]

# File lib/discordrb/bot.rb, line 647
def add_thread_member(channel, member)
  API::Channel.add_thread_member(@token, channel.resolve_id, member.resolve_id)
  nil
end
all_emoji(id = nil)
Alias for: emoji
away()
Alias for: idle
bot_app()
Alias for: bot_application
bot_application() click to toggle source

The bot’s OAuth application. @return [Application, nil] The bot’s application info. Returns ‘nil` if bot is not a bot account.

# File lib/discordrb/bot.rb, line 236
def bot_application
  return unless @type == :bot

  response = API.oauth_application(token)
  Application.new(JSON.parse(response), self)
end
Also aliased as: bot_app
bot_user()
Alias for: profile
competing=(name) click to toggle source

Sets the currently competing status to the specified name. @param name [String] The name of the game to be competing in. @return [String] The game that is being competed in now.

# File lib/discordrb/bot.rb, line 597
def competing=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 5)
end
connected?() click to toggle source

@return [true, false] whether or not the bot is currently connected to Discord.

# File lib/discordrb/bot.rb, line 296
def connected?
  @gateway.open?
end
create_oauth_application(name, redirect_uris) click to toggle source

Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using Discord. For information how to use this, see the docs: discord.com/developers/docs/topics/oauth2 @param name [String] What your application should be called. @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing. @return [Array(String, String)] your applications’ client ID and client secret to be used in OAuth authorization.

# File lib/discordrb/bot.rb, line 477
def create_oauth_application(name, redirect_uris)
  response = JSON.parse(API.create_oauth_application(@token, name, redirect_uris))
  [response['id'], response['secret']]
end
create_server(name, region = :'eu-central') click to toggle source

Creates a server on Discord with a specified name and a region. @note Discord’s API doesn’t directly return the server when creating it, so this method

waits until the data has been received via the websocket. This may make the execution take a while.

@param name [String] The name the new server should have. Doesn’t have to be alphanumeric. @param region [Symbol] The region where the server should be created, for example ‘eu-central’ or ‘hongkong’. @return [Server] The server that was created.

# File lib/discordrb/bot.rb, line 464
def create_server(name, region = :'eu-central')
  response = API::Server.create(token, name, region)
  id = JSON.parse(response)['id'].to_i
  sleep 0.1 until (server = @servers[id])
  debug "Successfully created server #{server.id} with name #{server.name}"
  server
end
debug(message) click to toggle source

@see Logger#debug

# File lib/discordrb/bot.rb, line 760
def debug(message)
  LOGGER.debug(message)
end
debug=(new_debug) click to toggle source

Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.

# File lib/discordrb/bot.rb, line 661
def debug=(new_debug)
  LOGGER.debug = new_debug
end
delete_application_command(command_id, server_id: nil) click to toggle source

Remove an application command from the commands registered with discord. @param command_id [String, Integer] The ID of the command to remove. @param server_id [String, Integer] The ID of the server to delete this command from, global if ‘nil`.

# File lib/discordrb/bot.rb, line 879
def delete_application_command(command_id, server_id: nil)
  if server_id
    API::Application.delete_guild_command(@token, profile.id, server_id, command_id)
  else
    API::Application.delete_global_command(@token, profile.id, command_id)
  end
end
delete_invite(code) click to toggle source

Revokes an invite to a server. Will fail unless you have the *Manage Server* permission. It is recommended that you use {Invite#delete} instead. @param code [String, Invite] The invite to revoke. For possible formats see {#resolve_invite_code}.

# File lib/discordrb/bot.rb, line 384
def delete_invite(code)
  invite = resolve_invite_code(code)
  API::Invite.delete(token, invite)
end
dispatch(type, data) click to toggle source

Dispatches an event to this bot. Called by the gateway connection handler used internally.

# File lib/discordrb/bot.rb, line 770
def dispatch(type, data)
  handle_dispatch(type, data)
end
dnd() click to toggle source

Sets the bot’s status to DnD (red icon).

# File lib/discordrb/bot.rb, line 619
def dnd
  gateway_check
  update_status(:dnd, @activity, nil)
end
edit_application_command(command_id, server_id: nil, name: nil, description: nil, default_permission: nil, type: :chat_input) { |builder, permission_builder| ... } click to toggle source

@yieldparam [OptionBuilder] @yieldparam [PermissionBuilder]

# File lib/discordrb/bot.rb, line 852
def edit_application_command(command_id, server_id: nil, name: nil, description: nil, default_permission: nil, type: :chat_input)
  type = ApplicationCommand::TYPES[type] || type

  builder = Interactions::OptionBuilder.new
  permission_builder = Interactions::PermissionBuilder.new

  yield(builder, permission_builder) if block_given?

  resp = if server_id
           API::Application.edit_guild_command(@token, profile.id, server_id, command_id, name, description, builder.to_a, default_permission, type)
         else
           API::Application.edit_guild_command(@token, profile.id, command_id, name, description, builder.to_a, default_permission.type)
         end
  cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)

  if permission_builder.to_a.any?
    raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id

    edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
  end

  cmd
end
edit_application_command_permissions(command_id, server_id, permissions = []) { |builder| ... } click to toggle source

@param command_id [Integer, String] @param server_id [Integer, String] @param permissions [Array<Hash>] An array of objects formatted as ‘{ id: ENTITY_ID, type: 1 or 2, permission: true or false }`

# File lib/discordrb/bot.rb, line 890
def edit_application_command_permissions(command_id, server_id, permissions = [])
  builder = Interactions::PermissionBuilder.new
  yield builder if block_given?

  permissions += builder.to_a
  API::Application.edit_guild_command_permissions(@token, profile.id, server_id, command_id, permissions)
end
emoji(id = nil) click to toggle source

@overload emoji(id)

Return an emoji by its ID
@param id [String, Integer] The emoji's ID.
@return [Emoji, nil] the emoji object. `nil` if the emoji was not found.

@overload emoji

The list of emoji the bot can use.
@return [Array<Emoji>] the emoji available.
# File lib/discordrb/bot.rb, line 201
def emoji(id = nil)
  emoji_hash = servers.values.map(&:emoji).reduce(&:merge)
  if id
    id = id.resolve_id
    emoji_hash[id]
  else
    emoji_hash.values
  end
end
Also aliased as: emojis, all_emoji
emojis(id = nil)
Alias for: emoji
find_emoji(name) click to toggle source

Finds an emoji by its name. @param name [String] The emoji name that should be resolved. @return [GlobalEmoji, nil] the emoji identified by the name, or ‘nil` if it couldn’t be found.

# File lib/discordrb/bot.rb, line 217
def find_emoji(name)
  LOGGER.out("Resolving emoji #{name}")
  emoji.find { |element| element.name == name }
end
game=(name) click to toggle source

Sets the currently playing game to the specified game. @param name [String] The name of the game to be played. @return [String] The game that is being played now.

# File lib/discordrb/bot.rb, line 561
def game=(name)
  gateway_check
  update_status(@status, name, nil)
end
Also aliased as: playing=
get_application_command(command_id, server_id: nil) click to toggle source

Get an application command by ID. @param command_id [String, Integer] @param server_id [String, Integer, nil] The ID of the server to get the command from. Global if ‘nil`.

# File lib/discordrb/bot.rb, line 804
def get_application_command(command_id, server_id: nil)
  resp = if server_id
           API::Application.get_guild_command(@token, profile.id, server_id, command_id)
         else
           API::Application.get_global_command(@token, profile.id, command_id)
         end
  ApplicationCommand.new(JSON.parse(resp), self, server_id)
end
get_application_commands(server_id: nil) click to toggle source

Get all application commands. @param server_id [String, Integer, nil] The ID of the server to get the commands from. Global if ‘nil`. @return [Array<ApplicationCommand>]

# File lib/discordrb/bot.rb, line 789
def get_application_commands(server_id: nil)
  resp = if server_id
           API::Application.get_guild_commands(@token, profile.id, server_id)
         else
           API::Application.get_global_commands(@token, profile.id)
         end

  JSON.parse(resp).map do |command_data|
    ApplicationCommand.new(command_data, self, server_id)
  end
end
idle() click to toggle source

Sets status to idle.

# File lib/discordrb/bot.rb, line 611
def idle
  gateway_check
  update_status(:idle, @activity, nil)
end
Also aliased as: away
ignore_user(user) click to toggle source

Add a user to the list of ignored users. Those users will be ignored in message events at event processing level. @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and

presence and any other events will still be received.

@param user [User, String, Integer] The user, or its ID, to be ignored.

# File lib/discordrb/bot.rb, line 742
def ignore_user(user)
  @ignored_ids << user.resolve_id
end
ignored?(user) click to toggle source

Checks whether a user is being ignored. @param user [User, String, Integer] The user, or its ID, to check. @return [true, false] whether or not the user is ignored.

# File lib/discordrb/bot.rb, line 755
def ignored?(user)
  @ignored_ids.include?(user.resolve_id)
end
invisible() click to toggle source

Sets the bot’s status to invisible (appears offline).

# File lib/discordrb/bot.rb, line 625
def invisible
  gateway_check
  update_status(:invisible, @activity, nil)
end
invite_url(server: nil, permission_bits: nil) click to toggle source

Creates an OAuth invite URL that can be used to invite this bot to a particular server. @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created. @param permission_bits [String, Integer] Permission bits that should be appended to invite url. @return [String] the OAuth invite URL.

# File lib/discordrb/bot.rb, line 311
def invite_url(server: nil, permission_bits: nil)
  @client_id ||= bot_application.id

  server_id_str = server ? "&guild_id=#{server.id}" : ''
  permission_bits_str = permission_bits ? "&permissions=#{permission_bits}" : ''
  "https://discord.com/oauth2/authorize?&client_id=#{@client_id}#{server_id_str}#{permission_bits_str}&scope=bot"
end
join() click to toggle source

Joins the bot’s connection thread with the current thread. This blocks execution until the websocket stops, which should only happen manually triggered. or due to an error. This is necessary to have a continuously running bot.

# File lib/discordrb/bot.rb, line 283
def join
  @gateway.sync
end
Also aliased as: sync
join_thread(channel) click to toggle source

Join a thread @param channel [Channel, Integer, String]

# File lib/discordrb/bot.rb, line 632
def join_thread(channel)
  API::Channel.join_thread(@token, channel.resolve_id)
  nil
end
leave_thread(channel) click to toggle source

Leave a thread @param channel [Channel, Integer, String]

# File lib/discordrb/bot.rb, line 639
def leave_thread(channel)
  API::Channel.leave_thread(@token, channel.resolve_id)
  nil
end
listening=(name) click to toggle source

Sets the current listening status to the specified name. @param name [String] The thing to be listened to. @return [String] The thing that is now being listened to.

# File lib/discordrb/bot.rb, line 571
def listening=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 2)
end
log_exception(e) click to toggle source

@see Logger#log_exception

# File lib/discordrb/bot.rb, line 765
def log_exception(e)
  LOGGER.log_exception(e)
end
mode=(new_mode) click to toggle source

Sets the logging mode @see Logger#mode=

# File lib/discordrb/bot.rb, line 667
def mode=(new_mode)
  LOGGER.mode = new_mode
end
on()
Alias for: online
online() click to toggle source

Sets status to online.

# File lib/discordrb/bot.rb, line 603
def online
  gateway_check
  update_status(:online, @activity, @streamurl)
end
Also aliased as: on
parse_mention(mention, server = nil) click to toggle source

Gets the user, channel, role or emoji from a string. @param mention [String] The mention, which should look like ‘<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`. @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up) @return [User, Channel, Role, Emoji] The user, channel, role or emoji identified by the mention, or `nil` if none exists.

# File lib/discordrb/bot.rb, line 530
def parse_mention(mention, server = nil)
  parse_mentions(mention, server).first
end
parse_mentions(mentions, server = nil) click to toggle source

Gets the users, channels, roles and emoji from a string. @param mentions [String] The mentions, which should look like ‘<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`. @param server [Server, nil] The server of the associated mentions. (recommended for role parsing, to speed things up) @return [Array<User, Channel, Role, Emoji>] The array of users, channels, roles and emoji identified by the mentions, or `nil` if none exists.

# File lib/discordrb/bot.rb, line 496
def parse_mentions(mentions, server = nil)
  array_to_return = []
  # While possible mentions may be in message
  while mentions.include?('<') && mentions.include?('>')
    # Removing all content before the next possible mention
    mentions = mentions.split('<', 2)[1]
    # Locate the first valid mention enclosed in `<...>`, otherwise advance to the next open `<`
    next unless mentions.split('>', 2).first.length < mentions.split('<', 2).first.length

    # Store the possible mention value to be validated with RegEx
    mention = mentions.split('>', 2).first
    if /@!?(?<id>\d+)/ =~ mention
      array_to_return << user(id) unless user(id).nil?
    elsif /#(?<id>\d+)/ =~ mention
      array_to_return << channel(id, server) unless channel(id, server).nil?
    elsif /@&(?<id>\d+)/ =~ mention
      if server
        array_to_return << server.role(id) unless server.role(id).nil?
      else
        @servers.each_value do |element|
          array_to_return << element.role(id) unless element.role(id).nil?
        end
      end
    elsif /(?<animated>^a|^${0}):(?<name>\w+):(?<id>\d+)/ =~ mention
      array_to_return << (emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil))
    end
  end
  array_to_return
end
playing=(name)
Alias for: game=
profile() click to toggle source

The bot’s user profile. This special user object can be used to edit user data like the current username (see {Profile#username=}). @return [Profile] The bot’s profile that can be used to edit data.

# File lib/discordrb/bot.rb, line 225
def profile
  return @profile if @profile

  response = Discordrb::API::User.profile(@token)
  @profile = Profile.new(JSON.parse(response), self)
end
Also aliased as: bot_user
prune_empty_groups() click to toggle source

Makes the bot leave any groups with no recipients remaining

# File lib/discordrb/bot.rb, line 780
def prune_empty_groups
  @channels.each_value do |channel|
    channel.leave_group if channel.group? && channel.recipients.empty?
  end
end
raise_heartbeat_event() click to toggle source

Raises a heartbeat event. Called by the gateway connection handler used internally.

# File lib/discordrb/bot.rb, line 775
def raise_heartbeat_event
  raise_event(HeartbeatEvent.new(self))
end
raw_token() click to toggle source

@return [String] the raw token, without any prefix @see token

# File lib/discordrb/bot.rb, line 255
def raw_token
  @token.split(' ').last
end
register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input) { |builder, permission_builder| ... } click to toggle source

@yieldparam [OptionBuilder] @yieldparam [PermissionBuilder] @example

bot.register_application_command(:reddit, 'Reddit Commands') do |cmd|
  cmd.subcommand_group(:subreddit, 'Subreddit Commands') do |group|
    group.subcommand(:hot, "What's trending") do |sub|
      sub.string(:subreddit, 'Subreddit to search')
    end
    group.subcommand(:new, "What's new") do |sub|
      sub.string(:since, 'How long ago', choices: ['this hour', 'today', 'this week', 'this month', 'this year', 'all time'])
      sub.string(:subreddit, 'Subreddit to search')
    end
  end
end
# File lib/discordrb/bot.rb, line 827
def register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input)
  type = ApplicationCommand::TYPES[type] || type

  builder = Interactions::OptionBuilder.new
  permission_builder = Interactions::PermissionBuilder.new
  yield(builder, permission_builder) if block_given?

  resp = if server_id
           API::Application.create_guild_command(@token, profile.id, server_id, name, description, builder.to_a, default_permission, type)
         else
           API::Application.create_global_command(@token, profile.id, name, description, builder.to_a, default_permission, type)
         end
  cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)

  if permission_builder.to_a.any?
    raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id

    edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
  end

  cmd
end
remove_thread_member(channel, member) click to toggle source

Remove a member from a thread @param channel [Channel, Integer, String] @param member [Member, Integer, String]

# File lib/discordrb/bot.rb, line 655
def remove_thread_member(channel, member)
  API::Channel.remove_thread_member(@token, channel.resolve_id, member.resolve_id)
  nil
end
run(background = false) click to toggle source

Runs the bot, which logs into Discord and connects the WebSocket. This prevents all further execution unless it is executed with ‘background` = `true`. @param background [true, false] If it is `true`, then the bot will run in

another thread to allow further execution. If it is `false`, this method
will block until {#stop} is called. If the bot is run with `true`, make
sure to eventually call {#join} so the script doesn't stop prematurely.

@note Running the bot in the background means that you can call some

methods that require a gateway connection *before* that connection is
established. In most cases an exception will be raised if you try to do
this. If you need a way to safely run code after the bot is fully
connected, use a {#ready} event handler instead.
# File lib/discordrb/bot.rb, line 271
def run(background = false)
  @gateway.run_async
  return if background

  debug('Oh wait! Not exiting yet as run was run synchronously.')
  @gateway.sync
end
send_file(channel, file, caption: nil, tts: false, filename: nil, spoiler: nil) click to toggle source

Sends a file to a channel. If it is an image, it will automatically be embedded. @note This executes in a blocking way, so if you’re sending long files, be wary of delays. @param channel [Channel, String, Integer] The channel, or its ID, to send something to. @param file [File] The file that should be sent. @param caption [string] The caption for the file. @param tts [true, false] Whether or not this file’s caption should be sent using Discord text-to-speech. @param filename [String] Overrides the filename of the uploaded file @param spoiler [true, false] Whether or not this file should appear as a spoiler. @example Send a file from disk

bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))
# File lib/discordrb/bot.rb, line 442
def send_file(channel, file, caption: nil, tts: false, filename: nil, spoiler: nil)
  if file.respond_to?(:read)
    if spoiler
      filename ||= File.basename(file.path)
      filename = "SPOILER_#{filename}" unless filename.start_with? 'SPOILER_'
    end
    # https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
    file.define_singleton_method(:original_filename) { filename } if filename
    file.define_singleton_method(:path) { filename } if filename
  end

  channel = channel.resolve_id
  response = API::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
  Message.new(JSON.parse(response), self)
end
send_message(channel, content, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil) click to toggle source

Sends a text message to a channel given its ID and the message’s content. @param channel [Channel, String, Integer] The channel, or its ID, to send something to. @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed). @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech. @param embeds [Hash, Discordrb::Webhooks::Embed, Array<Hash>, Array<Discordrb::Webhooks::Embed> nil] The rich embed(s) to append to this message. @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. ‘false` disables all pings @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any. @param components [View, Array<Hash>] Interaction components to associate with this message. @return [Message] The message that was sent.

# File lib/discordrb/bot.rb, line 398
def send_message(channel, content, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
  channel = channel.resolve_id
  debug("Sending message to #{channel} with content '#{content}'")
  allowed_mentions = { parse: [] } if allowed_mentions == false
  message_reference = { message_id: message_reference.id } if message_reference.respond_to?(:id)
  embeds = (embeds.instance_of?(Array) ? embeds.map(&:to_hash) : [embeds&.to_hash]).compact

  response = API::Channel.create_message(token, channel, content, tts, embeds, nil, attachments, allowed_mentions&.to_hash, message_reference, components)
  Message.new(JSON.parse(response), self)
end
send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil) click to toggle source

Sends a text message to a channel given its ID and the message’s content, then deletes it after the specified timeout in seconds. @param channel [Channel, String, Integer] The channel, or its ID, to send something to. @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed). @param timeout [Float] The amount of time in seconds after which the message sent will be deleted. @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech. @param embeds [Hash, Discordrb::Webhooks::Embed, Array<Hash>, Array<Discordrb::Webhooks::Embed> nil] The rich embed(s) to append to this message. @param attachments [Array<File>] Files that can be referenced in embeds via ‘attachment://file.png` @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any. @param components [View, Array<Hash>] Interaction components to associate with this message.

# File lib/discordrb/bot.rb, line 420
def send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
  Thread.new do
    Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"

    message = send_message(channel, content, tts, embeds, attachments, allowed_mentions, message_reference, components)
    sleep(timeout)
    message.delete
  end

  nil
end
servers() click to toggle source

The list of servers the bot is currently in. @return [Hash<Integer => Server>] The servers by ID.

# File lib/discordrb/bot.rb, line 180
def servers
  gateway_check
  unavailable_servers_check
  @servers
end
stop(_no_sync = nil) click to toggle source

Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that Discord is immediately aware of the closed connection and makes the bot appear offline instantly. @note This method no longer takes an argument as of 3.4.0

# File lib/discordrb/bot.rb, line 291
def stop(_no_sync = nil)
  @gateway.stop
end
stream(name, url) click to toggle source

Sets the currently online stream to the specified name and Twitch URL. @param name [String] The name of the stream to display. @param url [String] The url of the current Twitch stream. @return [String] The stream name that is being displayed now.

# File lib/discordrb/bot.rb, line 588
def stream(name, url)
  gateway_check
  update_status(@status, name, url)
  name
end
suppress_ready_debug() click to toggle source

Prevents the READY packet from being printed regardless of debug mode.

# File lib/discordrb/bot.rb, line 672
def suppress_ready_debug
  @prevent_ready = true
end
sync()
Alias for: join
thread_members() click to toggle source

The list of members in threads the bot can see. @return [Hash<Integer => Hash<Integer => Hash<String => Object>>]

# File lib/discordrb/bot.rb, line 188
def thread_members
  gateway_check
  unavailable_servers_check
  @thread_members
end
token() click to toggle source

The Discord API token received when logging in. Useful to explicitly call {API} methods. @return [String] The API token.

# File lib/discordrb/bot.rb, line 248
def token
  API.bot_name = @name
  @token
end
unignore_user(user) click to toggle source

Remove a user from the ignore list. @param user [User, String, Integer] The user, or its ID, to be unignored.

# File lib/discordrb/bot.rb, line 748
def unignore_user(user)
  @ignored_ids.delete(user.resolve_id)
end
update_oauth_application(name, redirect_uris, description = '', icon = nil) click to toggle source

Changes information about your OAuth application @param name [String] What your application should be called. @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing. @param description [String] A string that describes what your application does. @param icon [String, nil] A data URI for your icon image (for example a base 64 encoded image), or nil if no icon

should be set or changed.
# File lib/discordrb/bot.rb, line 488
def update_oauth_application(name, redirect_uris, description = '', icon = nil)
  API.update_oauth_application(@token, name, redirect_uris, description, icon)
end
update_status(status, activity, url, since = 0, afk = false, activity_type = 0) click to toggle source

Updates presence status. @param status [String] The status the bot should show up as. Can be ‘online`, `dnd`, `idle`, or `invisible` @param activity [String, nil] The name of the activity to be played/watched/listened to/stream name on the stream. @param url [String, nil] The Twitch URL to display as a stream. nil for no stream. @param since [Integer] When this status was set. @param afk [true, false] Whether the bot is AFK. @param activity_type [Integer] The type of activity status to display.

Can be 0 (Playing), 1 (Streaming), 2 (Listening), 3 (Watching), or 5 (Competing).

@see Gateway#send_status_update

# File lib/discordrb/bot.rb, line 543
def update_status(status, activity, url, since = 0, afk = false, activity_type = 0)
  gateway_check

  @activity = activity
  @status = status
  @streamurl = url
  type = url ? 1 : activity_type

  activity_obj = activity || url ? { 'name' => activity, 'url' => url, 'type' => type } : nil
  @gateway.send_status_update(status, since, activity_obj, afk)

  # Update the status in the cache
  profile.update_presence('status' => status.to_s, 'activities' => [activity_obj].compact)
end
users() click to toggle source

The list of users the bot shares a server with. @return [Hash<Integer => User>] The users by ID.

# File lib/discordrb/bot.rb, line 172
def users
  gateway_check
  unavailable_servers_check
  @users
end
voice(thing) click to toggle source

Gets the voice bot for a particular server or channel. You can connect to a new channel using the {#voice_connect} method. @param thing [Channel, Server, Integer] the server or channel you want to get the voice bot for, or its ID. @return [Voice::VoiceBot, nil] the VoiceBot for the thing you specified, or nil if there is no connection yet

# File lib/discordrb/bot.rb, line 326
def voice(thing)
  id = thing.resolve_id
  return @voices[id] if @voices[id]

  channel = channel(id)
  return nil unless channel

  server_id = channel.server.id
  return @voices[server_id] if @voices[server_id]
end
voice_connect(chan, encrypted = true) click to toggle source

Connects to a voice channel, initializes network connections and returns the {Voice::VoiceBot} over which audio data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already connected to voice, the existing connection will be terminated - you don’t have to call {Discordrb::Voice::VoiceBot#destroy} before calling this method. @param chan [Channel, String, Integer] The voice channel, or its ID, to connect to. @param encrypted [true, false] Whether voice communication should be encrypted using

(uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)

@return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.

# File lib/discordrb/bot.rb, line 345
def voice_connect(chan, encrypted = true)
  raise ArgumentError, 'Unencrypted voice connections are no longer supported.' unless encrypted

  chan = channel(chan.resolve_id)
  server_id = chan.server.id

  if @voices[chan.id]
    debug('Voice bot exists already! Destroying it')
    @voices[chan.id].destroy
    @voices.delete(chan.id)
  end

  debug("Got voice channel: #{chan}")

  @should_connect_to_voice[server_id] = chan
  @gateway.send_voice_state_update(server_id.to_s, chan.id.to_s, false, false)

  debug('Voice channel init packet sent! Now waiting.')

  sleep(0.05) until @voices[server_id]
  debug('Voice connect succeeded!')
  @voices[server_id]
end
voice_destroy(server, destroy_vws = true) click to toggle source

Disconnects the client from a specific voice connection given the server ID. Usually it’s more convenient to use {Discordrb::Voice::VoiceBot#destroy} rather than this. @param server [Server, String, Integer] The server, or server ID, the voice connection is on. @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you’re calling this method

directly, you should leave it as true.
# File lib/discordrb/bot.rb, line 374
def voice_destroy(server, destroy_vws = true)
  server = server.resolve_id
  @gateway.send_voice_state_update(server.to_s, nil, false, false)
  @voices[server].destroy if @voices[server] && destroy_vws
  @voices.delete(server)
end
watching=(name) click to toggle source

Sets the current watching status to the specified name. @param name [String] The thing to be watched. @return [String] The thing that is now being watched.

# File lib/discordrb/bot.rb, line 579
def watching=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 3)
end

Private Instance Methods

add_guild_member(data) click to toggle source

Internal handler for GUILD_MEMBER_ADD

# File lib/discordrb/bot.rb, line 1081
def add_guild_member(data)
  server_id = data['guild_id'].to_i
  server = self.server(server_id)

  member = Member.new(data, server, self)
  server.add_member(member)
end
add_message_reaction(data) click to toggle source

Internal handler for MESSAGE_REACTION_ADD

# File lib/discordrb/bot.rb, line 1183
def add_message_reaction(data); end
add_recipient(data) click to toggle source

Internal handler for CHANNEL_RECIPIENT_ADD

# File lib/discordrb/bot.rb, line 1061
def add_recipient(data)
  channel_id = data['channel_id'].to_i
  channel = self.channel(channel_id)

  recipient_user = ensure_user(data['user'])
  recipient = Recipient.new(recipient_user, channel, self)
  channel.add_recipient(recipient)
end
add_user_ban(data) click to toggle source

Internal handler for GUILD_BAN_ADD

# File lib/discordrb/bot.rb, line 1192
def add_user_ban(data); end
calculate_intents(intents) click to toggle source
# File lib/discordrb/bot.rb, line 1707
def calculate_intents(intents)
  intents.reduce(0) do |sum, intent|
    case intent
    when Symbol
      if INTENTS[intent]
        sum | INTENTS[intent]
      else
        LOGGER.warn("Unknown intent: #{intent}")
        sum
      end
    when Integer
      sum | intent
    else
      LOGGER.warn("Invalid intent: #{intent}")
      sum
    end
  end
end
call_event(handler, event) click to toggle source
# File lib/discordrb/bot.rb, line 1675
def call_event(handler, event)
  t = Thread.new do
    @event_threads ||= []
    @current_thread ||= 0

    @event_threads << t
    Thread.current[:discordrb_name] = "et-#{@current_thread += 1}"
    begin
      handler.call(event)
      handler.after_call(event)
    rescue StandardError => e
      log_exception(e)
    ensure
      @event_threads.delete(t)
    end
  end
end
create_channel(data) click to toggle source

Internal handler for CHANNEL_CREATE

# File lib/discordrb/bot.rb, line 1018
def create_channel(data)
  channel = data.is_a?(Discordrb::Channel) ? data : Channel.new(data, self)
  server = channel.server

  # Handle normal and private channels separately
  if server
    server.add_channel(channel)
    @channels[channel.id] = channel
  elsif channel.private?
    @pm_channels[channel.recipient.id] = channel
  elsif channel.group?
    @channels[channel.id] = channel
  end
end
create_guild(data) click to toggle source

Internal handler for GUILD_CREATE

# File lib/discordrb/bot.rb, line 1115
def create_guild(data)
  ensure_server(data, true)
end
create_guild_role(data) click to toggle source

Internal handler for GUILD_ROLE_CREATE

# File lib/discordrb/bot.rb, line 1142
def create_guild_role(data)
  role_data = data['role']
  server_id = data['guild_id'].to_i
  server = @servers[server_id]
  new_role = Role.new(role_data, self, server)
  existing_role = server.role(new_role.id)
  if existing_role
    existing_role.update_from(new_role)
  else
    server.add_role(new_role)
  end
end
create_message(data) click to toggle source

Internal handler for MESSAGE_CREATE

# File lib/discordrb/bot.rb, line 1171
def create_message(data); end
delete_channel(data) click to toggle source

Internal handler for CHANNEL_DELETE

# File lib/discordrb/bot.rb, line 1043
def delete_channel(data)
  channel = Channel.new(data, self)
  server = channel.server

  # Handle normal and private channels separately
  if server
    @channels.delete(channel.id)
    server.delete_channel(channel.id)
  elsif channel.pm?
    @pm_channels.delete(channel.recipient.id)
  elsif channel.group?
    @channels.delete(channel.id)
  end

  @thread_members.delete(channel.id) if channel.thread?
end
delete_guild(data) click to toggle source

Internal handler for GUILD_DELETE

# File lib/discordrb/bot.rb, line 1125
def delete_guild(data)
  id = data['id'].to_i
  @servers.delete(id)
end
delete_guild_member(data) click to toggle source

Internal handler for GUILD_MEMBER_DELETE

# File lib/discordrb/bot.rb, line 1103
def delete_guild_member(data)
  server_id = data['guild_id'].to_i
  server = self.server(server_id)
  return unless server

  user_id = data['user']['id'].to_i
  server.delete_member(user_id)
rescue Discordrb::Errors::NoPermission
  Discordrb::LOGGER.warn("delete_guild_member attempted to access a server for which the bot doesn't have permission! Not sure what happened here, ignoring")
end
delete_guild_role(data) click to toggle source

Internal handler for GUILD_ROLE_DELETE

# File lib/discordrb/bot.rb, line 1156
def delete_guild_role(data)
  role_id = data['role_id'].to_i
  server_id = data['guild_id'].to_i
  server = @servers[server_id]
  server.delete_role(role_id)
end
delete_message(data) click to toggle source

Internal handler for MESSAGE_DELETE

# File lib/discordrb/bot.rb, line 1180
def delete_message(data); end
gateway_check() click to toggle source

Throws a useful exception if there’s currently no gateway connection.

# File lib/discordrb/bot.rb, line 901
def gateway_check
  raise "A gateway connection is necessary to call this method! You'll have to do it inside any event (e.g. `ready`) or after `bot.run :async`." unless connected?
end
handle_awaits(event) click to toggle source
# File lib/discordrb/bot.rb, line 1693
def handle_awaits(event)
  @awaits ||= {}
  @awaits.each do |_, await|
    key, should_delete = await.match(event)
    next unless key

    debug("should_delete: #{should_delete}")
    @awaits.delete(await.key) if should_delete

    await_event = Discordrb::Events::AwaitEvent.new(await, event, self)
    raise_event(await_event)
  end
end
handle_dispatch(type, data) click to toggle source
# File lib/discordrb/bot.rb, line 1213
def handle_dispatch(type, data)
  # Check whether there are still unavailable servers and there have been more than 10 seconds since READY
  if @unavailable_servers&.positive? && (Time.now - @unavailable_timeout_time) > 10 && !((@intents || 0) & INTENTS[:servers]).zero?
    # The server streaming timed out!
    LOGGER.debug("Server streaming timed out with #{@unavailable_servers} servers remaining")
    LOGGER.debug('Calling ready now because server loading is taking a long time. Servers may be unavailable due to an outage, or your bot is on very large servers.')

    # Unset the unavailable server count so this doesn't get triggered again
    @unavailable_servers = 0

    notify_ready
  end

  case type
  when :READY
    # As READY may be called multiple times over a single process lifetime, we here need to reset the cache entirely
    # to prevent possible inconsistencies, like objects referencing old versions of other objects which have been
    # replaced.
    init_cache

    @profile = Profile.new(data['user'], self)

    # Initialize servers
    @servers = {}

    # Count unavailable servers
    @unavailable_servers = 0

    data['guilds'].each do |element|
      # Check for true specifically because unavailable=false indicates that a previously unavailable server has
      # come online
      if element['unavailable']
        @unavailable_servers += 1

        # Ignore any unavailable servers
        next
      end

      ensure_server(element, true)
    end

    # Add PM and group channels
    data['private_channels'].each do |element|
      channel = ensure_channel(element)
      if channel.pm?
        @pm_channels[channel.recipient.id] = channel
      else
        @channels[channel.id] = channel
      end
    end

    # Don't notify yet if there are unavailable servers because they need to get available before the bot truly has
    # all the data
    if @unavailable_servers.zero?
      # No unavailable servers - we're ready!
      notify_ready
    end

    @ready_time = Time.now
    @unavailable_timeout_time = Time.now
  when :GUILD_MEMBERS_CHUNK
    id = data['guild_id'].to_i
    server = server(id)
    server.process_chunk(data['members'], data['chunk_index'], data['chunk_count'])
  when :INVITE_CREATE
    invite = Invite.new(data, self)
    raise_event(InviteCreateEvent.new(data, invite, self))
  when :INVITE_DELETE
    raise_event(InviteDeleteEvent.new(data, self))
  when :MESSAGE_CREATE
    if ignored?(data['author']['id'])
      debug("Ignored author with ID #{data['author']['id']}")
      return
    end

    if @ignore_bots && data['author']['bot']
      debug("Ignored Bot account with ID #{data['author']['id']}")
      return
    end

    # If create_message is overwritten with a method that returns the parsed message, use that instead, so we don't
    # parse the message twice (which is just thrown away performance)
    message = create_message(data)
    message = Message.new(data, self) unless message.is_a? Message

    return if message.from_bot? && !should_parse_self

    # Dispatch a ChannelCreateEvent for channels we don't have cached
    if message.channel.private? && @pm_channels[message.channel.recipient.id].nil?
      create_channel(message.channel)

      raise_event(ChannelCreateEvent.new(message.channel, self))
    end

    event = MessageEvent.new(message, self)
    raise_event(event)

    if message.mentions.any? { |user| user.id == @profile.id }
      event = MentionEvent.new(message, self)
      raise_event(event)
    end

    if message.channel.private?
      event = PrivateMessageEvent.new(message, self)
      raise_event(event)
    end
  when :MESSAGE_UPDATE
    update_message(data)

    message = Message.new(data, self)

    event = MessageUpdateEvent.new(message, self)
    raise_event(event)

    return if message.from_bot? && !should_parse_self

    unless message.author
      LOGGER.debug("Edited a message with nil author! Content: #{message.content.inspect}, channel: #{message.channel.inspect}")
      return
    end

    event = MessageEditEvent.new(message, self)
    raise_event(event)
  when :MESSAGE_DELETE
    delete_message(data)

    event = MessageDeleteEvent.new(data, self)
    raise_event(event)
  when :MESSAGE_DELETE_BULK
    debug("MESSAGE_DELETE_BULK will raise #{data['ids'].length} events")

    data['ids'].each do |single_id|
      # Form a data hash for a single ID so the methods get what they want
      single_data = {
        'id' => single_id,
        'channel_id' => data['channel_id']
      }

      # Raise as normal
      delete_message(single_data)

      event = MessageDeleteEvent.new(single_data, self)
      raise_event(event)
    end
  when :TYPING_START
    start_typing(data)

    begin
      event = TypingEvent.new(data, self)
      raise_event(event)
    rescue Discordrb::Errors::NoPermission
      debug 'Typing started in channel the bot has no access to, ignoring'
    end
  when :MESSAGE_REACTION_ADD
    add_message_reaction(data)

    return if profile.id == data['user_id'].to_i && !should_parse_self

    event = ReactionAddEvent.new(data, self)
    raise_event(event)
  when :MESSAGE_REACTION_REMOVE
    remove_message_reaction(data)

    return if profile.id == data['user_id'].to_i && !should_parse_self

    event = ReactionRemoveEvent.new(data, self)
    raise_event(event)
  when :MESSAGE_REACTION_REMOVE_ALL
    remove_all_message_reactions(data)

    event = ReactionRemoveAllEvent.new(data, self)
    raise_event(event)
  when :PRESENCE_UPDATE
    # Ignore friends list presences
    return unless data['guild_id']

    new_activities = (data['activities'] || []).map { |act_data| Activity.new(act_data, self) }
    presence_user = @users[data['user']['id'].to_i]
    old_activities = (presence_user&.activities || [])
    update_presence(data)

    # Starting a new game
    playing_change = new_activities.reject do |act|
      old_activities.find { |old| old.name == act.name }
    end

    # Exiting an existing game
    playing_change += old_activities.reject do |old|
      new_activities.find { |act| act.name == old.name }
    end

    if playing_change.any?
      playing_change.each do |act|
        raise_event(PlayingEvent.new(data, act, self))
      end
    else
      raise_event(PresenceEvent.new(data, self))
    end
  when :VOICE_STATE_UPDATE
    old_channel_id = update_voice_state(data)

    event = VoiceStateUpdateEvent.new(data, old_channel_id, self)
    raise_event(event)
  when :VOICE_SERVER_UPDATE
    update_voice_server(data)

    event = VoiceServerUpdateEvent.new(data, self)
    raise_event(event)
  when :CHANNEL_CREATE
    create_channel(data)

    event = ChannelCreateEvent.new(data, self)
    raise_event(event)
  when :CHANNEL_UPDATE
    update_channel(data)

    event = ChannelUpdateEvent.new(data, self)
    raise_event(event)
  when :CHANNEL_DELETE
    delete_channel(data)

    event = ChannelDeleteEvent.new(data, self)
    raise_event(event)
  when :CHANNEL_RECIPIENT_ADD
    add_recipient(data)

    event = ChannelRecipientAddEvent.new(data, self)
    raise_event(event)
  when :CHANNEL_RECIPIENT_REMOVE
    remove_recipient(data)

    event = ChannelRecipientRemoveEvent.new(data, self)
    raise_event(event)
  when :GUILD_MEMBER_ADD
    add_guild_member(data)

    event = ServerMemberAddEvent.new(data, self)
    raise_event(event)
  when :GUILD_MEMBER_UPDATE
    update_guild_member(data)

    event = ServerMemberUpdateEvent.new(data, self)
    raise_event(event)
  when :GUILD_MEMBER_REMOVE
    delete_guild_member(data)

    event = ServerMemberDeleteEvent.new(data, self)
    raise_event(event)
  when :GUILD_BAN_ADD
    add_user_ban(data)

    event = UserBanEvent.new(data, self)
    raise_event(event)
  when :GUILD_BAN_REMOVE
    remove_user_ban(data)

    event = UserUnbanEvent.new(data, self)
    raise_event(event)
  when :GUILD_ROLE_UPDATE
    update_guild_role(data)

    event = ServerRoleUpdateEvent.new(data, self)
    raise_event(event)
  when :GUILD_ROLE_CREATE
    create_guild_role(data)

    event = ServerRoleCreateEvent.new(data, self)
    raise_event(event)
  when :GUILD_ROLE_DELETE
    delete_guild_role(data)

    event = ServerRoleDeleteEvent.new(data, self)
    raise_event(event)
  when :GUILD_CREATE
    create_guild(data)

    # Check for false specifically (no data means the server has never been unavailable)
    if data['unavailable'].is_a? FalseClass
      @unavailable_servers -= 1 if @unavailable_servers
      @unavailable_timeout_time = Time.now

      notify_ready if @unavailable_servers.zero?

      # Return here so the event doesn't get triggered
      return
    end

    event = ServerCreateEvent.new(data, self)
    raise_event(event)
  when :GUILD_UPDATE
    update_guild(data)

    event = ServerUpdateEvent.new(data, self)
    raise_event(event)
  when :GUILD_DELETE
    delete_guild(data)

    if data['unavailable'].is_a? TrueClass
      LOGGER.warn("Server #{data['id']} is unavailable due to an outage!")
      return # Don't raise an event
    end

    event = ServerDeleteEvent.new(data, self)
    raise_event(event)
  when :GUILD_EMOJIS_UPDATE
    server_id = data['guild_id'].to_i
    server = @servers[server_id]
    old_emoji_data = server.emoji.clone
    update_guild_emoji(data)
    new_emoji_data = server.emoji

    created_ids = new_emoji_data.keys - old_emoji_data.keys
    deleted_ids = old_emoji_data.keys - new_emoji_data.keys
    updated_ids = old_emoji_data.select do |k, v|
      new_emoji_data[k] && (v.name != new_emoji_data[k].name || v.roles != new_emoji_data[k].roles)
    end.keys

    event = ServerEmojiChangeEvent.new(server, data, self)
    raise_event(event)

    created_ids.each do |e|
      event = ServerEmojiCreateEvent.new(server, new_emoji_data[e], self)
      raise_event(event)
    end

    deleted_ids.each do |e|
      event = ServerEmojiDeleteEvent.new(server, old_emoji_data[e], self)
      raise_event(event)
    end

    updated_ids.each do |e|
      event = ServerEmojiUpdateEvent.new(server, old_emoji_data[e], new_emoji_data[e], self)
      raise_event(event)
    end
  when :INTERACTION_CREATE
    event = InteractionCreateEvent.new(data, self)
    raise_event(event)

    case data['type']
    when Interaction::TYPES[:command]
      event = ApplicationCommandEvent.new(data, self)

      Thread.new do
        Thread.current[:discordrb_name] = "it-#{event.interaction.id}"

        begin
          debug("Executing application command #{event.command_name}:#{event.command_id}")

          @application_commands[event.command_name]&.call(event)
        rescue StandardError => e
          log_exception(e)
        end
      end
    when Interaction::TYPES[:component]
      case data['data']['component_type']
      when Webhooks::View::COMPONENT_TYPES[:button]
        event = ButtonEvent.new(data, self)

        raise_event(event)
      when Webhooks::View::COMPONENT_TYPES[:string_select]
        event = StringSelectEvent.new(data, self)

        raise_event(event)
      when Webhooks::View::COMPONENT_TYPES[:user_select]
        event = UserSelectEvent.new(data, self)

        raise_event(event)
      when Webhooks::View::COMPONENT_TYPES[:role_select]
        event = RoleSelectEvent.new(data, self)

        raise_event(event)
      when Webhooks::View::COMPONENT_TYPES[:mentionable_select]
        event = MentionableSelectEvent.new(data, self)

        raise_event(event)
      when Webhooks::View::COMPONENT_TYPES[:channel_select]
        event = ChannelSelectEvent.new(data, self)

        raise_event(event)
      end
    when Interaction::TYPES[:modal_submit]

      event = ModalSubmitEvent.new(data, self)
      raise_event(event)
    end
  when :WEBHOOKS_UPDATE
    event = WebhookUpdateEvent.new(data, self)
    raise_event(event)
  when :THREAD_CREATE
    create_channel(data)

    event = ThreadCreateEvent.new(data, self)
    raise_event(event)
  when :THREAD_UPDATE
    update_channel(data)

    event = ThreadUpdateEvent.new(data, self)
    raise_event(event)
  when :THREAD_DELETE
    delete_channel(data)
    @thread_members.delete(data['id']&.resolve_id)

    # raise ThreadDeleteEvent
  when :THREAD_LIST_SYNC
    data['members'].map { |member| ensure_thread_member(member) }
    data['threads'].map { |channel| ensure_channel(channel, data['guild_id']) }

    # raise ThreadListSyncEvent?
  when :THREAD_MEMBER_UPDATE
    ensure_thread_member(data)
  when :THREAD_MEMBERS_UPDATE
    data['added_members']&.each do |added_member|
      ensure_thread_member(added_member) if added_member['user_id']
    end

    data['removed_member_ids']&.each do |member_id|
      @thread_members[data['id']&.resolve_id]&.delete(member_id&.resolve_id)
    end

    event = ThreadMembersUpdateEvent.new(data, self)
    raise_event(event)
  else
    # another event that we don't support yet
    debug "Event #{type} has been received but is unsupported. Raising UnknownEvent"

    event = UnknownEvent.new(type, data, self)
    raise_event(event)
  end

  # The existence of this array is checked before for performance reasons, since this has to be done for *every*
  # dispatch.
  if @event_handlers && @event_handlers[RawEvent]
    event = RawEvent.new(type, data, self)
    raise_event(event)
  end
rescue Exception => e
  LOGGER.error('Gateway message error!')
  log_exception(e)
end
notify_ready() click to toggle source

Notifies everything there is to be notified that the connection is now ready

# File lib/discordrb/bot.rb, line 1654
def notify_ready
  # Make sure to raise the event
  raise_event(ReadyEvent.new(self))
  LOGGER.good 'Ready'

  @gateway.notify_ready
end
process_token(type, token) click to toggle source
######   #### ##    ##

## ## ## ## ### ## ## ## ## #### ## ## ## #### ## ## ## ## ## ## ## ## ## #### ## ## ## ## ## ###

######   #### ##    ##
# File lib/discordrb/bot.rb, line 1205
def process_token(type, token)
  # Remove the "Bot " prefix if it exists
  token = token[4..] if token.start_with? 'Bot '

  token = "Bot #{token}" unless type == :user
  token
end
raise_event(event) click to toggle source
# File lib/discordrb/bot.rb, line 1662
def raise_event(event)
  debug("Raised a #{event.class}")
  handle_awaits(event)

  @event_handlers ||= {}
  handlers = @event_handlers[event.class]
  return unless handlers

  handlers.dup.each do |handler|
    call_event(handler, event) if handler.matches?(event)
  end
end
remove_all_message_reactions(data) click to toggle source

Internal handler for MESSAGE_REACTION_REMOVE_ALL

# File lib/discordrb/bot.rb, line 1189
def remove_all_message_reactions(data); end
remove_message_reaction(data) click to toggle source

Internal handler for MESSAGE_REACTION_REMOVE

# File lib/discordrb/bot.rb, line 1186
def remove_message_reaction(data); end
remove_recipient(data) click to toggle source

Internal handler for CHANNEL_RECIPIENT_REMOVE

# File lib/discordrb/bot.rb, line 1071
def remove_recipient(data)
  channel_id = data['channel_id'].to_i
  channel = self.channel(channel_id)

  recipient_user = ensure_user(data['user'])
  recipient = Recipient.new(recipient_user, channel, self)
  channel.remove_recipient(recipient)
end
remove_user_ban(data) click to toggle source

Internal handler for GUILD_BAN_REMOVE

# File lib/discordrb/bot.rb, line 1195
def remove_user_ban(data); end
start_typing(data) click to toggle source

Internal handler for TYPING_START

# File lib/discordrb/bot.rb, line 1174
def start_typing(data); end
unavailable_servers_check() click to toggle source

Logs a warning if there are servers which are still unavailable. e.g. due to a Discord outage or because the servers are large and taking a while to load.

# File lib/discordrb/bot.rb, line 907
def unavailable_servers_check
  # Return unless there are servers that are unavailable.
  return unless @unavailable_servers&.positive?

  LOGGER.warn("#{@unavailable_servers} servers haven't been cached yet.")
  LOGGER.warn('Servers may be unavailable due to an outage, or your bot is on very large servers that are taking a while to load.')
end
update_channel(data) click to toggle source

Internal handler for CHANNEL_UPDATE

# File lib/discordrb/bot.rb, line 1034
def update_channel(data)
  channel = Channel.new(data, self)
  old_channel = @channels[channel.id]
  return unless old_channel

  old_channel.update_from(channel)
end
update_guild(data) click to toggle source

Internal handler for GUILD_UPDATE

# File lib/discordrb/bot.rb, line 1120
def update_guild(data)
  @servers[data['id'].to_i].update_data(data)
end
update_guild_emoji(data) click to toggle source

Internal handler for GUILD_EMOJIS_UPDATE

# File lib/discordrb/bot.rb, line 1164
def update_guild_emoji(data)
  server_id = data['guild_id'].to_i
  server = @servers[server_id]
  server.update_emoji_data(data)
end
update_guild_member(data) click to toggle source

Internal handler for GUILD_MEMBER_UPDATE

# File lib/discordrb/bot.rb, line 1090
def update_guild_member(data)
  server_id = data['guild_id'].to_i
  server = self.server(server_id)

  member = server.member(data['user']['id'].to_i)
  member.update_roles(data['roles'])
  member.update_nick(data['nick'])
  member.update_global_name(data['user']['global_name']) if data['user']['global_name']
  member.update_boosting_since(data['premium_since'])
  member.update_communication_disabled_until(data['communication_disabled_until'])
end
update_guild_role(data) click to toggle source

Internal handler for GUILD_ROLE_UPDATE

# File lib/discordrb/bot.rb, line 1131
def update_guild_role(data)
  role_data = data['role']
  server_id = data['guild_id'].to_i
  server = @servers[server_id]
  new_role = Role.new(role_data, self, server)
  role_id = role_data['id'].to_i
  old_role = server.roles.find { |r| r.id == role_id }
  old_role.update_from(new_role)
end
update_message(data) click to toggle source

Internal handler for MESSAGE_UPDATE

# File lib/discordrb/bot.rb, line 1177
def update_message(data); end
update_presence(data) click to toggle source

Internal handler for PRESENCE_UPDATE

# File lib/discordrb/bot.rb, line 924
def update_presence(data)
  # Friends list presences have no server ID so ignore these to not cause an error
  return unless data['guild_id']

  user_id = data['user']['id'].to_i
  server_id = data['guild_id'].to_i
  server = server(server_id)
  return unless server

  member_is_new = false

  if server.member_cached?(user_id)
    member = server.member(user_id)
  else
    # If the member is not cached yet, it means that it just came online from not being cached at all
    # due to large_threshold. Fortunately, Discord sends the entire member object in this case, and
    # not just a part of it - we can just cache this member directly
    member = Member.new(data, server, self)
    debug("Implicitly adding presence-obtained member #{user_id} to #{server_id} cache")

    member_is_new = true
  end

  username = data['user']['username']
  if username && !member_is_new # Don't set the username for newly-cached members
    debug "Implicitly updating presence-obtained information username for member #{user_id}"
    member.update_username(username)
  end

  global_name = data['user']['global_name']
  if global_name && !member_is_new # Don't set the global_name for newly-cached members
    debug "Implicitly updating presence-obtained information global_name for member #{user_id}"
    member.update_global_name(global_name)
  end

  member.update_presence(data)

  member.avatar_id = data['user']['avatar'] if data['user']['avatar']

  server.cache_member(member)
end
update_voice_server(data) click to toggle source

Internal handler for VOICE_SERVER_UPDATE

# File lib/discordrb/bot.rb, line 995
def update_voice_server(data)
  server_id = data['guild_id'].to_i
  channel = @should_connect_to_voice[server_id]

  debug("Voice server update received! chan: #{channel.inspect}")
  return unless channel

  @should_connect_to_voice.delete(server_id)
  debug('Updating voice server!')

  token = data['token']
  endpoint = data['endpoint']

  unless endpoint
    debug('VOICE_SERVER_UPDATE sent with nil endpoint! Ignoring')
    return
  end

  debug('Got data, now creating the bot.')
  @voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint)
end
update_voice_state(data) click to toggle source

Internal handler for VOICE_STATE_UPDATE

# File lib/discordrb/bot.rb, line 967
def update_voice_state(data)
  @session_id = data['session_id']

  server_id = data['guild_id'].to_i
  server = server(server_id)
  return unless server

  user_id = data['user_id'].to_i
  old_voice_state = server.voice_states[user_id]
  old_channel_id = old_voice_state.voice_channel&.id if old_voice_state

  server.update_voice_state(data)

  existing_voice = @voices[server_id]
  if user_id == @profile.id && existing_voice
    new_channel_id = data['channel_id']
    if new_channel_id
      new_channel = channel(new_channel_id)
      existing_voice.channel = new_channel
    else
      voice_destroy(server_id)
    end
  end

  old_channel_id
end