Class: Discordrb::Bot

Inherits:
Object
  • Object
show all
Includes:
Events
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 Events

matches_all

Constructor Details

#initialize(email, password, debug = false) ⇒ Bot

Makes a new bot with the given email and password. It will be ready to be added event handlers to and can eventually be run with #run.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/discordrb/bot.rb', line 70

def initialize(email, password, debug = false)
  # Make sure people replace the login details in the example files...
  if email.end_with? 'example.com'
    puts 'You have to replace the login details in the example files with your own!'
    exit
  end

  LOGGER.debug = debug
  @should_parse_self = false

  @email = email
  @password = password

  debug('Creating token cache')
  @token_cache = Discordrb::TokenCache.new
  debug('Token cache created successfully')
  @token = 

  @event_handlers = {}

  @channels = {}
  @users = {}

  @awaits = {}

  @event_threads = []
  @current_thread = 0
end

Instance Attribute Details

#bot_userUser (readonly)

The user that represents the bot itself. This version will always be identical to the user determined by #user called with the bot's ID.



36
37
38
# File 'lib/discordrb/bot.rb', line 36

def bot_user
  @bot_user
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.



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

def event_threads
  @event_threads
end

#profileProfile (readonly)

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



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

def profile
  @profile
end

#serversArray<Server> (readonly)

The list of servers the bot is currently in.



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

def servers
  @servers
end

#should_parse_selfObject

Whether or not the bot should parse its own messages. Off by default.



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

def should_parse_self
  @should_parse_self
end

#tokenString (readonly)

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



41
42
43
# File 'lib/discordrb/bot.rb', line 41

def token
  @token
end

#usersArray<User> (readonly)

The list of users the bot shares a server with.



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

def users
  @users
end

#voiceObject (readonly)

Returns the value of attribute voice.



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

def voice
  @voice
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.



336
337
338
339
340
# File 'lib/discordrb/bot.rb', line 336

def add_await(key, type, attributes = {}, &block)
  fail "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
  await = Await.new(self, key, type, attributes, block)
  @awaits[key] = await
end

#add_handler(handler) ⇒ Object Also known as: <<



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

def add_handler(handler)
  clazz = event_class(handler.class)
  @event_handlers[clazz] << handler
end

#await(attributes = {}) {|event| ... } ⇒ AwaitEventHandler

This event is raised when an Await is triggered. It provides an easy way to execute code on an await without having to rely on the await's block.

Options Hash (attributes):

  • :key (Symbol)

    Exactly matches the await's key.

  • :type (Class)

    Exactly matches the event's type.

Yields:

  • The block is executed when the event is raised.

Yield Parameters:

  • event (AwaitEvent)

    The event that was raised.



517
518
519
# File 'lib/discordrb/bot.rb', line 517

def await(attributes = {}, &block)
  register_event(AwaitEvent, attributes, block)
end

#channel(id) ⇒ Channel

Gets a channel given its ID. This queries the internal channel cache, and if the channel doesn't exist in there, it will get the data from Discord.



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

def channel(id)
  debug("Obtaining data for channel with id #{id}")
  return @channels[id] if @channels[id]

  response = API.channel(@token, id)
  channel = Channel.new(JSON.parse(response), self)
  @channels[id] = channel
end

#channel_create(attributes = {}, &block) ⇒ Object

Handle channel creation Attributes:

  • type: Channel type ('text' or 'voice')
  • name: Channel name


452
453
454
# File 'lib/discordrb/bot.rb', line 452

def channel_create(attributes = {}, &block)
  register_event(ChannelCreateEvent, attributes, block)
end

#channel_delete(attributes = {}, &block) ⇒ Object

Handle channel deletion Attributes:

  • type: Channel type ('text' or 'voice')
  • name: Channel name


468
469
470
# File 'lib/discordrb/bot.rb', line 468

def channel_delete(attributes = {}, &block)
  register_event(ChannelDeleteEvent, attributes, block)
end

#channel_update(attributes = {}, &block) ⇒ Object

Handle channel update Attributes:

  • type: Channel type ('text' or 'voice')
  • name: Channel name


460
461
462
# File 'lib/discordrb/bot.rb', line 460

def channel_update(attributes = {}, &block)
  register_event(ChannelUpdateEvent, attributes, block)
end

#create_server(name, region = :london) ⇒ 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.



356
357
358
359
360
361
362
363
# File 'lib/discordrb/bot.rb', line 356

def create_server(name, region = :london)
  response = API.create_server(@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, important = false) ⇒ Object



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

def debug(message, important = false)
  LOGGER.debug(message, important)
end

#debug=(new_debug) ⇒ Object

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



393
394
395
# File 'lib/discordrb/bot.rb', line 393

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.



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

def delete_invite(code)
  invite = resolve_invite_code(code)
  API.delete_invite(@token, invite)
end

#disconnected(attributes = {}, &block) ⇒ Object



428
429
430
# File 'lib/discordrb/bot.rb', line 428

def disconnected(attributes = {}, &block)
  register_event(DisconnectEvent, attributes, block)
end

#find(channel_name, server_name = nil, threshold = 0) ⇒ Array<Channel>

Finds a channel given its name and optionally the name of the server it is in. If the threshold is not 0, it will use a Levenshtein distance function to find the channel in a fuzzy way, which allows slight misspellings.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/discordrb/bot.rb', line 265

def find(channel_name, server_name = nil, threshold = 0)
  require 'levenshtein'

  results = []
  @servers.values.each do |server|
    server.channels.each do |channel|
      distance = Levenshtein.distance(channel.name, channel_name)
      distance += Levenshtein.distance(server_name || server.name, server.name)
      next if distance > threshold

      # Make a singleton accessor "distance"
      channel.instance_variable_set(:@distance, distance)
      class << channel
        attr_reader :distance
      end
      results << channel
    end
  end
  results
end

#find_user(username, threshold = 0) ⇒ Array<User>

Finds a user given its username. This allows fuzzy finding using Levenshtein distances, see #find



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

def find_user(username, threshold = 0)
  require 'levenshtein'

  results = []
  @users.values.each do |user|
    distance = Levenshtein.distance(user.username, username)
    next if distance > threshold

    # Make a singleton accessor "distance"
    user.instance_variable_set(:@distance, distance)
    class << user
      attr_reader :distance
    end
    results << user
  end
  results
end

#game=(name) ⇒ String

Sets the currently playing game to the specified game.



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

def game=(name)
  @game = name

  data = {
    'op' => 3,
    'd' => {
      'idle_since' => nil,
      'game' => name ? { 'name' => name } : nil
    }
  }

  @ws.send(data.to_json)
  name
end

#handler_class(event_class) ⇒ Object



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

def handler_class(event_class)
  class_from_string(event_class.to_s + 'Handler')
end

#join(invite) ⇒ Object

Makes the bot join an invite to a server.



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

def join(invite)
  invite = resolve_invite_code(invite)
  resolved = JSON.parse(API.resolve_invite(@token, invite))['code']
  API.join_server(@token, resolved)
end

#log_exception(e) ⇒ Object



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

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

#member_join(attributes = {}, &block) ⇒ Object



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

def member_join(attributes = {}, &block)
  register_event(GuildMemberAddEvent, attributes, block)
end

#member_leave(attributes = {}, &block) ⇒ Object



493
494
495
# File 'lib/discordrb/bot.rb', line 493

def member_leave(attributes = {}, &block)
  register_event(GuildMemberDeleteEvent, attributes, block)
end

#member_update(attributes = {}, &block) ⇒ Object



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

def member_update(attributes = {}, &block)
  register_event(GuildMemberUpdateEvent, attributes, block)
end

#mention(attributes = {}, &block) ⇒ Object



444
445
446
# File 'lib/discordrb/bot.rb', line 444

def mention(attributes = {}, &block)
  register_event(MentionEvent, attributes, block)
end

#message(attributes = {}) {|event| ... } ⇒ MessageEventHandler

This event is raised when a message is sent to a text channel the bot is currently in.

Options Hash (attributes):

  • :start_with (String, Regexp)

    Matches the string the message starts with.

  • :end_with (String, Regexp)

    Matches the string the message ends with.

  • :contains (String, Regexp)

    Matches a string the message contains.

  • :in (String, Integer, Channel)

    Matches the channel the message was sent in.

  • :from (String, Integer, User)

    Matches the user that sent the message.

  • :content (String)

    Exactly matches the entire content of the message.

  • :content (String)

    Exactly matches the entire content of the message.

  • :after (Time)

    Matches a time after the time the message was sent at.

  • :before (Time)

    Matches a time before the time the message was sent at.

  • :private (Boolean)

    Matches whether or not the channel is private.

Yields:

  • The block is executed when the event is raised.

Yield Parameters:



420
421
422
# File 'lib/discordrb/bot.rb', line 420

def message(attributes = {}, &block)
  register_event(MessageEvent, attributes, block)
end

#parse_mention(mention) ⇒ User

Gets the user from a mention of the user.



368
369
370
371
372
# File 'lib/discordrb/bot.rb', line 368

def parse_mention(mention)
  # Mention format: <@id>
  return nil unless /\<@(?<id>\d+)\>?/ =~ mention
  user(id.to_i)
end

#playing(attributes = {}, &block) ⇒ Object



440
441
442
# File 'lib/discordrb/bot.rb', line 440

def playing(attributes = {}, &block)
  register_event(PlayingEvent, attributes, block)
end

#pm(attributes = {}, &block) ⇒ Object Also known as: private_message



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

def pm(attributes = {}, &block)
  register_event(PrivateMessageEvent, attributes, block)
end

#presence(attributes = {}, &block) ⇒ Object



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

def presence(attributes = {}, &block)
  register_event(PresenceEvent, attributes, block)
end

#private_channel(id) ⇒ Channel

Creates a private channel for the given user ID, or if one exists already, returns that one. It is recommended that you use User#pm instead, as this is mainly for internal use. However, usage of this method may be unavoidable if only the user ID is known.



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

def private_channel(id)
  debug("Creating private channel with user id #{id}")
  return @private_channels[id] if @private_channels[id]

  response = API.create_private(@token, @bot_user.id, id)
  channel = Channel.new(JSON.parse(response), self)
  @private_channels[id] = channel
end

#ready(attributes = {}, &block) ⇒ Object



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

def ready(attributes = {}, &block)
  register_event(ReadyEvent, attributes, block)
end

#remove_handler(handler) ⇒ Object



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

def remove_handler(handler)
  clazz = event_class(handler.class)
  @event_handlers[clazz].delete(handler)
end

#resolve_invite_code(invite) ⇒ String

Gets the code for an invite.



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

def resolve_invite_code(invite)
  invite = invite.code if invite.is_a? Discordrb::Invite
  invite = invite[invite.rindex('/') + 1..-1] if invite.start_with?('http') || invite.start_with?('discord.gg')
  invite
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.



105
106
107
108
109
110
111
# File 'lib/discordrb/bot.rb', line 105

def run(async = false)
  run_async
  return if async

  debug('Oh wait! Not exiting yet as run was run synchronously.')
  sync
end

#run_asyncObject



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
# File 'lib/discordrb/bot.rb', line 113

def run_async
  # Handle heartbeats
  @heartbeat_interval = 1
  @heartbeat_active = false
  @heartbeat_thread = Thread.new do
    Thread.current[:discordrb_name] = 'heartbeat'
    loop do
      sleep @heartbeat_interval
      send_heartbeat if @heartbeat_active
    end
  end

  @ws_thread = Thread.new do
    Thread.current[:discordrb_name] = 'websocket'
    loop do
      websocket_connect
      debug('Disconnected! Attempting to reconnect in 5 seconds.')
      sleep 5
      @token = 
    end
  end

  debug('WS thread created! Now waiting for confirmation that everything worked')
  @ws_success = false
  sleep(0.5) until @ws_success
  debug('Confirmation received! Exiting run.')
end

#send_file(channel_id, file) ⇒ 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.



325
326
327
# File 'lib/discordrb/bot.rb', line 325

def send_file(channel_id, file)
  API.send_file(@token, channel_id, file)
end

#send_message(channel_id, content) ⇒ Message

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



314
315
316
317
318
319
# File 'lib/discordrb/bot.rb', line 314

def send_message(channel_id, content)
  debug("Sending message to #{channel_id} with content '#{content}'")

  response = API.send_message(@token, channel_id, content)
  Message.new(JSON.parse(response), self)
end

#server(id) ⇒ Server?

Note:

This can only resolve servers the bot is currently in.

Gets a server by its ID.



253
254
255
# File 'lib/discordrb/bot.rb', line 253

def server(id)
  @servers[id]
end

#server_create(attributes = {}, &block) ⇒ Object



497
498
499
# File 'lib/discordrb/bot.rb', line 497

def server_create(attributes = {}, &block)
  register_event(GuildCreateEvent, attributes, block)
end

#server_delete(attributes = {}, &block) ⇒ Object



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

def server_delete(attributes = {}, &block)
  register_event(GuildDeleteEvent, attributes, block)
end

#server_update(attributes = {}, &block) ⇒ Object



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

def server_update(attributes = {}, &block)
  register_event(GuildUpdateEvent, attributes, block)
end

#stopObject

Kills the websocket thread, stopping all connections to Discord.



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

def stop
  @ws_thread.kill
end

#syncObject

Prevents all further execution until the websocket thread stops (e. g. through a closed connection).



142
143
144
# File 'lib/discordrb/bot.rb', line 142

def sync
  @ws_thread.join
end

#typing(attributes = {}, &block) ⇒ Object



432
433
434
# File 'lib/discordrb/bot.rb', line 432

def typing(attributes = {}, &block)
  register_event(TypingEvent, attributes, block)
end

#user(id) ⇒ User?

Note:

This can only resolve users known by the bot (i.e. that share a server with the bot).

Gets a user by its ID.



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

def user(id)
  @users[id]
end

#voice_connect(channel) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/discordrb/bot.rb', line 203

def voice_connect(channel)
  if @voice
    debug('Voice bot exists already! Destroying it')
    @voice.destroy
    @voice = nil
  end

  @voice_channel = channel
  debug("Got voice channel: #{@voice_channel}")

  data = {
    op: 4,
    d: {
      guild_id: @voice_channel.server.id.to_s,
      channel_id: @voice_channel.id.to_s,
      self_mute: false,
      self_deaf: false
    }
  }
  debug("Voice channel init packet is: #{data.to_json}")

  @should_connect_to_voice = true
  @ws.send(data.to_json)
  debug('Voice channel init packet sent! Now waiting.')

  sleep(0.05) until @voice
  debug('Voice connect succeeded!')
  @voice
end

#voice_state_update(attributes = {}, &block) ⇒ Object

Handle a change to a voice state. This includes joining a voice channel or changing mute or deaf state. Attributes:

  • from: User whose voice state changed
  • mute: server mute status
  • deaf: server deaf status
  • self_mute: self mute status
  • self_deaf: self deaf status
  • channel: channel the user joined


481
482
483
# File 'lib/discordrb/bot.rb', line 481

def voice_state_update(attributes = {}, &block)
  register_event(VoiceStateUpdateEvent, attributes, block)
end