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_user, #find_channel, #find_user, #init_cache, #invite, #member, #pm_channel, #request_chunks, #resolve_invite_code, #server, #user, #voice_regions

Methods included from EventContainer

#add_handler, #await, #channel_create, #channel_delete, #channel_recipient_add, #channel_recipient_remove, #channel_update, class_from_string, #clear!, #disconnected, event_class, handler_class, #heartbeat, #include_events, #member_join, #member_leave, #member_update, #mention, #message, #message_delete, #message_edit, #playing, #pm, #presence, #raw, #reaction_add, #reaction_remove, #reaction_remove_all, #ready, #remove_handler, #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, #typing, #unknown, #user_ban, #user_unban, #voice_state_update, #webhook_update

Methods included from Events

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: :stream) ⇒ 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.


104
105
106
107
108
109
110
111
112
113
114
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
# File 'lib/discordrb/bot.rb', line 104

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: :stream
)
  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

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

  init_cache

  @voices = {}
  @should_connect_to_voice = {}

  @ignored_ids = Set.new
  @ignore_bots = ignore_bots

  @event_threads = []
  @current_thread = 0

  @status = :online
end

Instance Attribute Details

#awaitsHash<Symbol => Await> (readonly)


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

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.


44
45
46
# File 'lib/discordrb/bot.rb', line 44

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.


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

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.


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

def name
  @name
end

#shard_keyArray(Integer, Integer) (readonly)


55
56
57
# File 'lib/discordrb/bot.rb', line 55

def shard_key
  @shard_key
end

#should_parse_selftrue, false


47
48
49
# File 'lib/discordrb/bot.rb', line 47

def should_parse_self
  @should_parse_self
end

#voicesHash<Integer => VoiceBot> (readonly)


287
288
289
# File 'lib/discordrb/bot.rb', line 287

def voices
  @voices
end

Instance Method Details

#accept_invite(invite) ⇒ Object

Makes the bot join an invite to a server.


269
270
271
272
# File 'lib/discordrb/bot.rb', line 269

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.


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

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?

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

Options Hash (attributes):

  • :timeout (Numeric)

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

Raises:

  • (ArgumentError)

    if timeout is given and is not a positive numeric value


589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/discordrb/bot.rb', line 589

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 && timeout.is_a?(Numeric) && timeout <= 0

  mutex = Mutex.new
  cv = ConditionVariable.new
  response = nil
  block = lambda do |event|
    mutex.synchronize do
      response = event
      cv.signal
    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

#bot_applicationApplication? Also known as: bot_app

The bot's OAuth application.


204
205
206
207
208
# File 'lib/discordrb/bot.rb', line 204

def bot_application
  return unless @type == :bot
  response = API.oauth_application(token)
  Application.new(JSON.parse(response), self)
end

#connected?true, false


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

def connected?
  @gateway.open?
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://discordapp.com/developers/docs/topics/oauth2


424
425
426
427
# File 'lib/discordrb/bot.rb', line 424

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') ⇒ Server

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.

Creates a server on Discord with a specified name and a region.


410
411
412
413
414
415
416
417
# File 'lib/discordrb/bot.rb', line 410

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 @servers[id]
  server = @servers[id]
  debug "Successfully created server #{server.id} with name #{server.name}"
  server
end

#debug(message) ⇒ Object

See Also:

  • Logger#debug

643
644
645
# File 'lib/discordrb/bot.rb', line 643

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.


554
555
556
# File 'lib/discordrb/bot.rb', line 554

def debug=(new_debug)
  LOGGER.debug = new_debug
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.


352
353
354
355
# File 'lib/discordrb/bot.rb', line 352

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.


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

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

#dndObject

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


542
543
544
545
# File 'lib/discordrb/bot.rb', line 542

def dnd
  gateway_check
  update_status(:dnd, @activity, nil)
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.


168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/discordrb/bot.rb', line 168

def emoji(id = nil)
  gateway_check
  unavailable_servers_check

  emoji_hash = @servers.values.map(&:emoji).reduce(&:merge)
  if id
    id = id.resolve_id
    emoji_hash[id]
  else
    emoji_hash.values
  end
end

#find_emoji(name) ⇒ GlobalEmoji?

Finds an emoji by its name.


187
188
189
190
# File 'lib/discordrb/bot.rb', line 187

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.


489
490
491
492
493
# File 'lib/discordrb/bot.rb', line 489

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

#idleObject Also known as: away

Sets status to idle.


534
535
536
537
# File 'lib/discordrb/bot.rb', line 534

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.


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

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

#ignored?(user) ⇒ true, false

Checks whether a user is being ignored.


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

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

#invisibleObject

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


548
549
550
551
# File 'lib/discordrb/bot.rb', line 548

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

#invite_url(server: nil, permission_bits: nil) ⇒ String

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


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

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://discordapp.com/oauth2/authorize?&client_id=#{@client_id}#{server_id_str}#{permission_bits_str}&scope=bot"
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.


250
251
252
# File 'lib/discordrb/bot.rb', line 250

def join
  @gateway.sync
end

#listening=(name) ⇒ String

Sets the current listening status to the specified name.


500
501
502
503
504
# File 'lib/discordrb/bot.rb', line 500

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

#log_exception(e) ⇒ Object


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

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

#mode=(new_mode) ⇒ Object

Sets the logging mode

See Also:


560
561
562
# File 'lib/discordrb/bot.rb', line 560

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

#onlineObject Also known as: on

Sets status to online.


526
527
528
529
# File 'lib/discordrb/bot.rb', line 526

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 mention of the user, channel, role or emoji.


443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/discordrb/bot.rb', line 443

def parse_mention(mention, server = nil)
  # Mention format: <@id>
  if /<@!?(?<id>\d+)>/ =~ mention
    user(id)
  elsif /<#(?<id>\d+)>/ =~ mention
    channel(id, server)
  elsif /<@&(?<id>\d+)>/ =~ mention
    return server.role(id) if server
    @servers.values.each do |element|
      role = element.role(id)
      return role unless role.nil?
    end

    # Return nil if no role is found
    nil
  elsif /<(?<animated>a)?:(?<name>\w+):(?<id>\d+)>/ =~ mention
    emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil)
  end
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=).


195
196
197
198
# File 'lib/discordrb/bot.rb', line 195

def profile
  gateway_check
  @profile
end

#prune_empty_groupsObject

Makes the bot leave any groups with no recipients remaining


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

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.


658
659
660
# File 'lib/discordrb/bot.rb', line 658

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

#raw_tokenString

Returns the raw token, without any prefix

See Also:


222
223
224
# File 'lib/discordrb/bot.rb', line 222

def raw_token
  @token.split(' ').last
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 backround = true.


238
239
240
241
242
243
244
# File 'lib/discordrb/bot.rb', line 238

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

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

def send_file(channel, file, caption: nil, tts: false)
  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, embed = nil) ⇒ Message

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


363
364
365
366
367
368
369
# File 'lib/discordrb/bot.rb', line 363

def send_message(channel, content, tts = false, embed = nil)
  channel = channel.resolve_id
  debug("Sending message to #{channel} with content '#{content}'")

  response = API::Channel.create_message(token, channel, content, tts, embed ? embed.to_hash : nil)
  Message.new(JSON.parse(response), self)
end

#send_temporary_message(channel, content, timeout, tts = false, embed = nil) ⇒ 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.


378
379
380
381
382
383
384
385
386
387
388
# File 'lib/discordrb/bot.rb', line 378

def send_temporary_message(channel, content, timeout, tts = false, embed = nil)
  Thread.new do
    Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"

    message = send_message(channel, content, tts, embed)
    sleep(timeout)
    message.delete
  end

  nil
end

#serversHash<Integer => Server>

The list of servers the bot is currently in.


155
156
157
158
159
# File 'lib/discordrb/bot.rb', line 155

def servers
  gateway_check
  unavailable_servers_check
  @servers
end

#stop(no_sync = false) ⇒ Object

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.


258
259
260
# File 'lib/discordrb/bot.rb', line 258

def stop(no_sync = false)
  @gateway.stop(no_sync)
end

#stream(name, url) ⇒ String

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


519
520
521
522
523
# File 'lib/discordrb/bot.rb', line 519

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.


565
566
567
# File 'lib/discordrb/bot.rb', line 565

def suppress_ready_debug
  @prevent_ready = true
end

#tokenString

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


215
216
217
218
# File 'lib/discordrb/bot.rb', line 215

def token
  API.bot_name = @name
  @token
end

#unignore_user(user) ⇒ Object

Remove a user from the ignore list.


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

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


435
436
437
# File 'lib/discordrb/bot.rb', line 435

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.


471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/discordrb/bot.rb', line 471

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, 'game' => activity_obj)
end

#usersHash<Integer => User>

The list of users the bot shares a server with.


147
148
149
150
151
# File 'lib/discordrb/bot.rb', line 147

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.


293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/discordrb/bot.rb', line 293

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]

  nil
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.


314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/discordrb/bot.rb', line 314

def voice_connect(chan, encrypted = true)
  chan = channel(chan.resolve_id)
  server_id = chan.server.id
  @should_encrypt_voice = encrypted

  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.


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

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.


509
510
511
512
513
# File 'lib/discordrb/bot.rb', line 509

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