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_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) ⇒ 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.


100
101
102
103
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
# File 'lib/discordrb/bot.rb', line 100

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
)

  LOGGER.mode = if log_mode.is_a? TrueClass # Specifically check for `true` because people might not have updated yet
                  :debug
                else
                  log_mode
                end

  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

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

  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)


274
275
276
# File 'lib/discordrb/bot.rb', line 274

def voices
  @voices
end

Instance Method Details

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

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.


561
562
563
564
565
566
# File 'lib/discordrb/bot.rb', line 561

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

#bot_applicationApplication? Also known as: bot_app

The bot's OAuth application.


200
201
202
203
204
# File 'lib/discordrb/bot.rb', line 200

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

#connected?true, false


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

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


411
412
413
414
# File 'lib/discordrb/bot.rb', line 411

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.


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

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

590
591
592
# File 'lib/discordrb/bot.rb', line 590

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.


539
540
541
# File 'lib/discordrb/bot.rb', line 539

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.


339
340
341
342
# File 'lib/discordrb/bot.rb', line 339

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.


600
601
602
# File 'lib/discordrb/bot.rb', line 600

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

#dndObject

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


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

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.


165
166
167
168
169
170
171
172
173
174
175
# File 'lib/discordrb/bot.rb', line 165

def emoji(id = nil)
  gateway_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.


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

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.


474
475
476
477
478
# File 'lib/discordrb/bot.rb', line 474

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

#idleObject Also known as: away

Sets status to idle.


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

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.


572
573
574
# File 'lib/discordrb/bot.rb', line 572

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

#ignored?(user) ⇒ true, false

Checks whether a user is being ignored.


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

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

#invisibleObject

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


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

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.


265
266
267
268
269
270
271
# File 'lib/discordrb/bot.rb', line 265

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

#join(invite) ⇒ Object

Makes the bot join an invite to a server.


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

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

#listening=(name) ⇒ String

Sets the current listening status to the specified name.


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

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

#log_exception(e) ⇒ Object


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

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

#mode=(new_mode) ⇒ Object

Sets the logging mode

See Also:


545
546
547
# File 'lib/discordrb/bot.rb', line 545

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

#onlineObject Also known as: on

Sets status to online.


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

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

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

Gets the user, role or emoji from a mention of the user, role or emoji.


430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/discordrb/bot.rb', line 430

def parse_mention(mention, server = nil)
  # Mention format: <@id>
  if /<@!?(?<id>\d+)>/ =~ mention
    user(id)
  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 /<:(\w+):(?<id>\d+)>/ =~ mention
    emoji(id)
  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=).


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

def profile
  gateway_check
  @profile
end

#prune_empty_groupsObject

Makes the bot leave any groups with no recipients remaining


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

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.


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

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

#raw_tokenString

Returns the raw token, without any prefix.

See Also:


218
219
220
# File 'lib/discordrb/bot.rb', line 218

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

#run(async = false) ⇒ Object

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


228
229
230
231
232
233
234
# File 'lib/discordrb/bot.rb', line 228

def run(async = false)
  @gateway.run_async
  return if async

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

385
386
387
388
389
# File 'lib/discordrb/bot.rb', line 385

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.


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

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.


365
366
367
368
369
370
371
372
373
374
375
# File 'lib/discordrb/bot.rb', line 365

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.


153
154
155
156
# File 'lib/discordrb/bot.rb', line 153

def servers
  gateway_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.


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

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.


504
505
506
507
508
# File 'lib/discordrb/bot.rb', line 504

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.


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

def suppress_ready_debug
  @prevent_ready = true
end

#syncObject

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.


238
239
240
# File 'lib/discordrb/bot.rb', line 238

def sync
  @gateway.sync
end

#tokenString

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


211
212
213
214
# File 'lib/discordrb/bot.rb', line 211

def token
  API.bot_name = @name
  @token
end

#unignore_user(user) ⇒ Object

Remove a user from the ignore list.


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

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


422
423
424
# File 'lib/discordrb/bot.rb', line 422

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.


456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/discordrb/bot.rb', line 456

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.


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

def users
  gateway_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.


280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/discordrb/bot.rb', line 280

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.


301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/discordrb/bot.rb', line 301

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.


329
330
331
332
333
334
# File 'lib/discordrb/bot.rb', line 329

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.


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

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