Class: Discordrb::Bot

Inherits:
Object
  • Object
show all
Includes:
Cache, EventContainer
Defined in:
lib/discordrb/bot.rb

Overview

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

Direct Known Subclasses

Commands::CommandBot

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Cache

#channel, #ensure_channel, #ensure_server, #ensure_thread_member, #ensure_user, #find_channel, #find_user, #init_cache, #invite, #member, #pm_channel, #request_chunks, #resolve_invite_code, #server, #server_preview, #user, #voice_regions

Methods included from EventContainer

#add_handler, #application_command, #application_command_permissions_update, #autocomplete, #await, #button, #channel_create, #channel_delete, #channel_pins_update, #channel_recipient_add, #channel_recipient_remove, #channel_select, #channel_update, class_from_string, #clear!, #disconnected, event_class, handler_class, #heartbeat, #include_events, #integration_create, #integration_delete, #integration_update, #interaction_create, #invite_create, #invite_delete, #member_join, #member_leave, #member_update, #mention, #mentionable_select, #message, #message_delete, #message_edit, #message_update, #modal_submit, #playing, #pm, #presence, #raw, #reaction_add, #reaction_remove, #reaction_remove_all, #reaction_remove_emoji, #ready, #remove_application_command_handler, #remove_handler, #role_select, #server_create, #server_delete, #server_emoji, #server_emoji_create, #server_emoji_delete, #server_emoji_update, #server_role_create, #server_role_delete, #server_role_update, #server_update, #string_select, #typing, #unknown, #user_ban, #user_select, #user_unban, #voice_server_update, #voice_state_update, #webhook_update

Methods included from Events

#added_members, matches_all

Constructor Details

#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) ⇒ Bot

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 EventContainer#ready event, or use the #run method with the :async parameter and do the processing after that.

See Also:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/discordrb/bot.rb', line 115

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

Instance Attribute Details

#awaitsHash<Symbol => Await> (readonly)



64
65
66
# File 'lib/discordrb/bot.rb', line 64

def awaits
  @awaits
end

#event_threadsArray<Thread> (readonly)

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.



50
51
52
# File 'lib/discordrb/bot.rb', line 50

def event_threads
  @event_threads
end

#gatewayGateway (readonly)

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.



69
70
71
# File 'lib/discordrb/bot.rb', line 69

def gateway
  @gateway
end

#nameString

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.



58
59
60
# File 'lib/discordrb/bot.rb', line 58

def name
  @name
end

#shard_keyArray(Integer, Integer) (readonly)



61
62
63
# File 'lib/discordrb/bot.rb', line 61

def shard_key
  @shard_key
end

#should_parse_selftrue, false



53
54
55
# File 'lib/discordrb/bot.rb', line 53

def should_parse_self
  @should_parse_self
end

#voicesHash<Integer => VoiceBot> (readonly)



335
336
337
# File 'lib/discordrb/bot.rb', line 335

def voices
  @voices
end

Instance Method Details

#accept_invite(invite) ⇒ Object

Makes the bot join an invite to a server.



309
310
311
312
# File 'lib/discordrb/bot.rb', line 309

def accept_invite(invite)
  resolved = invite(invite).code
  API::Invite.accept(token, resolved)
end

#add_await(key, type, attributes = {}) {|event| ... } ⇒ Await

Deprecated.

Will be changed to blocking behavior in v4.0. Use #add_await! instead.

Add an await the bot should listen to. For information on awaits, see Await.

Yields:

  • Is executed when the await is triggered.

Yield Parameters:

  • event (Event)

    The event object that was triggered.



691
692
693
694
695
696
697
# File 'lib/discordrb/bot.rb', line 691

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| ... } ⇒ Event?

Awaits an event, blocking the current thread until a response is received.

Options Hash (attributes):

  • :timeout (Numeric)

    the amount of time (in seconds) to wait for a response before returning nil. Waits forever if omitted.

Yields:

  • Executed when a matching event is received.

Yield Parameters:

  • event (Event)

    The event object that was triggered.

Yield Returns:

  • (true, false)

    Whether the event matches extra await criteria described by the block

Raises:

  • (ArgumentError)

    if timeout is given and is not a positive numeric value



707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
# File 'lib/discordrb/bot.rb', line 707

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) ⇒ Object

Add a member to a thread



654
655
656
657
# File 'lib/discordrb/bot.rb', line 654

def add_thread_member(channel, member)
  API::Channel.add_thread_member(@token, channel.resolve_id, member.resolve_id)
  nil
end

#application_emoji(emoji_id) ⇒ Emoji

Fetches a single application emoji from its ID.



919
920
921
922
# File 'lib/discordrb/bot.rb', line 919

def application_emoji(emoji_id)
  response = API::Application.get_application_emoji(@token, profile.id, emoji_id.resolve_id)
  Emoji.new(JSON.parse(response), self)
end

#application_emojisArray<Emoji>

Fetches all the application emojis that the bot can use.



911
912
913
914
# File 'lib/discordrb/bot.rb', line 911

def application_emojis
  response = API::Application.list_application_emojis(@token, profile.id)
  JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) }
end

#bot_applicationApplication? Also known as: bot_app

The bot's OAuth application.



243
244
245
246
247
248
# File 'lib/discordrb/bot.rb', line 243

def bot_application
  return unless @type == :bot

  response = API.oauth_application(token)
  Application.new(JSON.parse(response), self)
end

#competing=(name) ⇒ String

Sets the currently competing status to the specified name.



604
605
606
607
# File 'lib/discordrb/bot.rb', line 604

def competing=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 5)
end

#connected?true, false



303
304
305
# File 'lib/discordrb/bot.rb', line 303

def connected?
  @gateway.open?
end

#create_application_emoji(name:, image:) ⇒ Emoji

Creates a new custom emoji that can be used by this application.



928
929
930
931
932
# File 'lib/discordrb/bot.rb', line 928

def create_application_emoji(name:, image:)
  image = image.respond_to?(:read) ? Discordrb.encode64(image) : image
  response = API::Application.create_application_emoji(@token, profile.id, name, image)
  Emoji.new(JSON.parse(response), self)
end

#create_oauth_application(name, redirect_uris) ⇒ Array(String, String)

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: https://discord.com/developers/docs/topics/oauth2



484
485
486
487
# File 'lib/discordrb/bot.rb', line 484

def create_oauth_application(name, redirect_uris)
  response = JSON.parse(API.create_oauth_application(@token, name, redirect_uris))
  [response['id'], response['secret']]
end

#debug(message) ⇒ Object

See Also:

  • Logger#debug


767
768
769
# File 'lib/discordrb/bot.rb', line 767

def debug(message)
  LOGGER.debug(message)
end

#debug=(new_debug) ⇒ Object

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



668
669
670
# File 'lib/discordrb/bot.rb', line 668

def debug=(new_debug)
  LOGGER.debug = new_debug
end

#delete_application_command(command_id, server_id: nil) ⇒ Object

Remove an application command from the commands registered with discord.



886
887
888
889
890
891
892
# File 'lib/discordrb/bot.rb', line 886

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_application_emoji(emoji_id) ⇒ Object

Deletes an existing application emoji.



945
946
947
# File 'lib/discordrb/bot.rb', line 945

def delete_application_emoji(emoji_id)
  API::Application.delete_application_emoji(@token, profile.id, emoji_id.resolve_id)
end

#delete_invite(code) ⇒ Object

Revokes an invite to a server. Will fail unless you have the Manage Server permission. It is recommended that you use Invite#delete instead.



399
400
401
402
# File 'lib/discordrb/bot.rb', line 399

def delete_invite(code)
  invite = resolve_invite_code(code)
  API::Invite.delete(token, invite)
end

#dispatch(type, data) ⇒ Object

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



777
778
779
# File 'lib/discordrb/bot.rb', line 777

def dispatch(type, data)
  handle_dispatch(type, data)
end

#dndObject

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



626
627
628
629
# File 'lib/discordrb/bot.rb', line 626

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, default_member_permissions: nil, contexts: nil, nsfw: nil) {|, | ... } ⇒ Object

Yield Parameters:

  • (OptionBuilder)
  • (PermissionBuilder)


859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
# File 'lib/discordrb/bot.rb', line 859

def edit_application_command(command_id, server_id: nil, name: nil, description: nil, default_permission: nil, type: :chat_input, default_member_permissions: nil, contexts: nil, nsfw: nil)
  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, default_member_permissions, contexts, nsfw)
         else
           API::Application.edit_global_command(@token, profile.id, command_id, name, description, builder.to_a, default_permission, type, default_member_permissions, contexts, nsfw)
         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 = [], bearer_token = nil) {|builder| ... } ⇒ Object

Yields:

  • (builder)

Raises:

  • (ArgumentError)


898
899
900
901
902
903
904
905
906
907
# File 'lib/discordrb/bot.rb', line 898

def edit_application_command_permissions(command_id, server_id, permissions = [], bearer_token = nil)
  builder = Interactions::PermissionBuilder.new
  yield builder if block_given?

  raise ArgumentError, 'This method requires a valid bearer token to be provided' unless bearer_token

  permissions += builder.to_a
  bearer_token = "Bearer #{bearer_token.delete_prefix('Bearer ')}"
  API::Application.edit_guild_command_permissions(bearer_token, profile.id, server_id, command_id, permissions)
end

#edit_application_emoji(emoji_id, name:) ⇒ Emoji

Edits an existing application emoji.



938
939
940
941
# File 'lib/discordrb/bot.rb', line 938

def edit_application_emoji(emoji_id, name:)
  response = API::Application.edit_application_emoji(@token, profile.id, emoji_id.resolve_id, name)
  Emoji.new(JSON.parse(response), self)
end

#emoji(id) ⇒ Emoji? #emojiArray<Emoji> Also known as: emojis, all_emoji

Overloads:

  • #emoji(id) ⇒ Emoji?

    Return an emoji by its ID

  • #emojiArray<Emoji>

    The list of emoji the bot can use.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/discordrb/bot.rb', line 202

def emoji(id = nil)
  if (id = id&.resolve_id)
    @servers.each_value do |server|
      emoji = server.emojis[id]
      return emoji if emoji
    end
  else
    hash = {}
    @servers.each_value do |server|
      hash.merge!(server.emojis)
    end

    hash
  end
end

#find_emoji(name) ⇒ GlobalEmoji?

Finds an emoji by its name.



224
225
226
227
# File 'lib/discordrb/bot.rb', line 224

def find_emoji(name)
  LOGGER.out("Resolving emoji #{name}")
  emoji.find { |element| element.name == name }
end

#game=(name) ⇒ String Also known as: playing=

Sets the currently playing game to the specified game.



568
569
570
571
# File 'lib/discordrb/bot.rb', line 568

def game=(name)
  gateway_check
  update_status(@status, name, nil)
end

#get_application_command(command_id, server_id: nil) ⇒ Object

Get an application command by ID.



811
812
813
814
815
816
817
818
# File 'lib/discordrb/bot.rb', line 811

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) ⇒ Array<ApplicationCommand>

Get all application commands.



796
797
798
799
800
801
802
803
804
805
806
# File 'lib/discordrb/bot.rb', line 796

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

#idleObject Also known as: away

Sets status to idle.



618
619
620
621
# File 'lib/discordrb/bot.rb', line 618

def idle
  gateway_check
  update_status(:idle, @activity, nil)
end

#ignore_user(user) ⇒ Object

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.

Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.



749
750
751
# File 'lib/discordrb/bot.rb', line 749

def ignore_user(user)
  @ignored_ids << user.resolve_id
end

#ignored?(user) ⇒ true, false

Checks whether a user is being ignored.



762
763
764
# File 'lib/discordrb/bot.rb', line 762

def ignored?(user)
  @ignored_ids.include?(user.resolve_id)
end

#invisibleObject

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



632
633
634
635
# File 'lib/discordrb/bot.rb', line 632

def invisible
  gateway_check
  update_status(:invisible, @activity, nil)
end

#invite_url(server: nil, permission_bits: nil, redirect_uri: nil, scopes: ['bot']) ⇒ String

Creates an OAuth invite URL that can be used to invite this bot to a particular server.



320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/discordrb/bot.rb', line 320

def invite_url(server: nil, permission_bits: nil, redirect_uri: nil, scopes: ['bot'])
  @client_id ||= bot_application.id

  query = URI.encode_www_form({
    client_id: @client_id,
    guild_id: server&.id,
    permissions: permission_bits,
    redirect_uri: redirect_uri,
    scope: scopes.join(' ')
  }.compact)

  "https://discord.com/oauth2/authorize?#{query}"
end

#joinObject Also known as: sync

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.



290
291
292
# File 'lib/discordrb/bot.rb', line 290

def join
  @gateway.sync
end

#join_thread(channel) ⇒ Object

Join a thread



639
640
641
642
# File 'lib/discordrb/bot.rb', line 639

def join_thread(channel)
  API::Channel.join_thread(@token, channel.resolve_id)
  nil
end

#leave_thread(channel) ⇒ Object

Leave a thread



646
647
648
649
# File 'lib/discordrb/bot.rb', line 646

def leave_thread(channel)
  API::Channel.leave_thread(@token, channel.resolve_id)
  nil
end

#listening=(name) ⇒ String

Sets the current listening status to the specified name.



578
579
580
581
# File 'lib/discordrb/bot.rb', line 578

def listening=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 2)
end

#log_exception(e) ⇒ Object



772
773
774
# File 'lib/discordrb/bot.rb', line 772

def log_exception(e)
  LOGGER.log_exception(e)
end

#mode=(new_mode) ⇒ Object

Sets the logging mode

See Also:



674
675
676
# File 'lib/discordrb/bot.rb', line 674

def mode=(new_mode)
  LOGGER.mode = new_mode
end

#onlineObject Also known as: on

Sets status to online.



610
611
612
613
# File 'lib/discordrb/bot.rb', line 610

def online
  gateway_check
  update_status(:online, @activity, @streamurl)
end

#parse_mention(mention, server = nil) ⇒ User, ...

Gets the user, channel, role or emoji from a string.



537
538
539
# File 'lib/discordrb/bot.rb', line 537

def parse_mention(mention, server = nil)
  parse_mentions(mention, server).first
end

#parse_mentions(mentions, server = nil) ⇒ Array<User, Channel, Role, Emoji>

Gets the users, channels, roles and emoji from a string.



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/discordrb/bot.rb', line 503

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 != '', 'name' => name, 'id' => id }, self, nil))
    end
  end
  array_to_return
end

#profileProfile Also known as: bot_user

The bot's user profile. This special user object can be used to edit user data like the current username (see Profile#username=).



232
233
234
235
236
237
# File 'lib/discordrb/bot.rb', line 232

def profile
  return @profile if @profile

  response = Discordrb::API::User.profile(@token)
  @profile = Profile.new(JSON.parse(response), self)
end

#prune_empty_groupsObject

Makes the bot leave any groups with no recipients remaining



787
788
789
790
791
# File 'lib/discordrb/bot.rb', line 787

def prune_empty_groups
  @channels.each_value do |channel|
    channel.leave_group if channel.group? && channel.recipients.empty?
  end
end

#raise_heartbeat_eventObject

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



782
783
784
# File 'lib/discordrb/bot.rb', line 782

def raise_heartbeat_event
  raise_event(HeartbeatEvent.new(self))
end

#raw_tokenString

Returns the raw token, without any prefix.

See Also:



262
263
264
# File 'lib/discordrb/bot.rb', line 262

def raw_token
  @token.split(' ').last
end

#register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input, default_member_permissions: nil, contexts: nil, nsfw: false) {|, | ... } ⇒ Object

Examples:

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

Yield Parameters:

  • (OptionBuilder)
  • (PermissionBuilder)


834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
# File 'lib/discordrb/bot.rb', line 834

def register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input, default_member_permissions: nil, contexts: nil, nsfw: false)
  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, default_member_permissions, contexts, nsfw)
         else
           API::Application.create_global_command(@token, profile.id, name, description, builder.to_a, default_permission, type, default_member_permissions, contexts, nsfw)
         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) ⇒ Object

Remove a member from a thread



662
663
664
665
# File 'lib/discordrb/bot.rb', line 662

def remove_thread_member(channel, member)
  API::Channel.remove_thread_member(@token, channel.resolve_id, member.resolve_id)
  nil
end

#run(background = false) ⇒ Object

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 EventContainer#ready event handler instead.

Runs the bot, which logs into Discord and connects the WebSocket. This prevents all further execution unless it is executed with background = true.



278
279
280
281
282
283
284
# File 'lib/discordrb/bot.rb', line 278

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) ⇒ Object

Note:

This executes in a blocking way, so if you're sending long files, be wary of delays.

Sends a file to a channel. If it is an image, it will automatically be embedded.

Examples:

Send a file from disk

bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))


463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/discordrb/bot.rb', line 463

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, flags = 0, nonce = nil, enforce_nonce = false) ⇒ Message

Sends a text message to a channel given its ID and the message's content.



416
417
418
419
420
421
422
423
424
425
# File 'lib/discordrb/bot.rb', line 416

def send_message(channel, content, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0, nonce = nil, enforce_nonce = false)
  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.resolve_id } if message_reference.respond_to?(:resolve_id)
  embeds = (embeds.instance_of?(Array) ? embeds.map(&:to_hash) : [embeds&.to_hash]).compact

  response = API::Channel.create_message(token, channel, content, tts, embeds, nonce, attachments, allowed_mentions&.to_hash, message_reference, components, flags, enforce_nonce)
  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, flags = 0, nonce = nil, enforce_nonce = false) ⇒ Object

Sends a text message to a channel given its ID and the message's content, then deletes it after the specified timeout in seconds.



441
442
443
444
445
446
447
448
449
450
451
# File 'lib/discordrb/bot.rb', line 441

def send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0, nonce = nil, enforce_nonce = false)
  Thread.new do
    Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"

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

  nil
end

#serversHash<Integer => Server>

The list of servers the bot is currently in.



181
182
183
184
185
# File 'lib/discordrb/bot.rb', line 181

def servers
  gateway_check
  unavailable_servers_check
  @servers
end

#stop(_no_sync = nil) ⇒ Object

Note:

This method no longer takes an argument as of 3.4.0

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.



298
299
300
# File 'lib/discordrb/bot.rb', line 298

def stop(_no_sync = nil)
  @gateway.stop
end

#stream(name, url) ⇒ String

Sets the currently online stream to the specified name and Twitch URL.



595
596
597
598
599
# File 'lib/discordrb/bot.rb', line 595

def stream(name, url)
  gateway_check
  update_status(@status, name, url)
  name
end

#suppress_ready_debugObject

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



679
680
681
# File 'lib/discordrb/bot.rb', line 679

def suppress_ready_debug
  @prevent_ready = true
end

#thread_membersHash<Integer => Hash<Integer => Hash<String => Object>>]

The list of members in threads the bot can see.



189
190
191
192
193
# File 'lib/discordrb/bot.rb', line 189

def thread_members
  gateway_check
  unavailable_servers_check
  @thread_members
end

#tokenString

The Discord API token received when logging in. Useful to explicitly call API methods.



255
256
257
258
# File 'lib/discordrb/bot.rb', line 255

def token
  API.bot_name = @name
  @token
end

#unignore_user(user) ⇒ Object

Remove a user from the ignore list.



755
756
757
# File 'lib/discordrb/bot.rb', line 755

def unignore_user(user)
  @ignored_ids.delete(user.resolve_id)
end

#update_oauth_application(name, redirect_uris, description = '', icon = nil) ⇒ Object

Changes information about your OAuth application



495
496
497
# File 'lib/discordrb/bot.rb', line 495

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) ⇒ Object

Updates presence status.



550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/discordrb/bot.rb', line 550

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

#usersHash<Integer => User>

The list of users the bot shares a server with.



173
174
175
176
177
# File 'lib/discordrb/bot.rb', line 173

def users
  gateway_check
  unavailable_servers_check
  @users
end

#voice(thing) ⇒ Voice::VoiceBot?

Gets the voice bot for a particular server or channel. You can connect to a new channel using the #voice_connect method.



341
342
343
344
345
346
347
348
349
350
# File 'lib/discordrb/bot.rb', line 341

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) ⇒ Voice::VoiceBot

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 Voice::VoiceBot#destroy before calling this method.

Raises:

  • (ArgumentError)


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/discordrb/bot.rb', line 360

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) ⇒ Object

Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use Voice::VoiceBot#destroy rather than this.



389
390
391
392
393
394
# File 'lib/discordrb/bot.rb', line 389

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) ⇒ String

Sets the current watching status to the specified name.



586
587
588
589
# File 'lib/discordrb/bot.rb', line 586

def watching=(name)
  gateway_check
  update_status(@status, name, nil, nil, nil, 3)
end