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.



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

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

  @debug = debug
  @should_parse_self = false

  @email = email
  @password = password

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



34
35
36
# File 'lib/discordrb/bot.rb', line 34

def bot_user
  @bot_user
end

#debug(message, important = false) ⇒ Object



470
471
472
# File 'lib/discordrb/bot.rb', line 470

def debug(message, important = false)
  puts "[DEBUG : #{Thread.current[:discordrb_name]} @ #{Time.now}] #{message}" if @debug || important
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.



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

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=).



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

def profile
  @profile
end

#serversArray<Server> (readonly)

The list of servers the bot is currently in.



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

def servers
  @servers
end

#should_parse_selfObject

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



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

def should_parse_self
  @should_parse_self
end

#tokenString (readonly)

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



39
40
41
# File 'lib/discordrb/bot.rb', line 39

def token
  @token
end

#usersArray<User> (readonly)

The list of users the bot shares a server with.



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

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



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

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



465
466
467
468
# File 'lib/discordrb/bot.rb', line 465

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.



450
451
452
# File 'lib/discordrb/bot.rb', line 450

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.



150
151
152
153
154
155
156
157
# File 'lib/discordrb/bot.rb', line 150

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


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

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


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

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


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

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.



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

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

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



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

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

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



365
366
367
# File 'lib/discordrb/bot.rb', line 365

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.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/discordrb/bot.rb', line 228

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

#game=(name_or_id) ⇒ Game

Sets the currently playing game to the specified game.



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/discordrb/bot.rb', line 315

def game=(name_or_id)
  game = Discordrb::Games.find_game(name_or_id)
  @game = game

  data = {
    'op' => 3,
    'd' => {
      'idle_since' => nil,
      'game_id' => game ? game.id : 60 # 60 blanks out the game playing
    }
  }

  @ws.send(data.to_json)
  game
end

#handler_class(event_class) ⇒ Object



479
480
481
# File 'lib/discordrb/bot.rb', line 479

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.



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

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



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

def log_exception(e)
  debug("Exception: #{e.inspect}", true)
  e.backtrace.each { |line| debug(line, true) }
end

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



418
419
420
# File 'lib/discordrb/bot.rb', line 418

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

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



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

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

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



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

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

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



377
378
379
# File 'lib/discordrb/bot.rb', line 377

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:



357
358
359
# File 'lib/discordrb/bot.rb', line 357

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

#parse_mention(mention) ⇒ User

Gets the user from a mention of the user.



306
307
308
309
310
# File 'lib/discordrb/bot.rb', line 306

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

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



454
455
456
# File 'lib/discordrb/bot.rb', line 454

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

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



373
374
375
# File 'lib/discordrb/bot.rb', line 373

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.



164
165
166
167
168
169
170
171
# File 'lib/discordrb/bot.rb', line 164

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



361
362
363
# File 'lib/discordrb/bot.rb', line 361

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

#remove_handler(handler) ⇒ Object



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

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.



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

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.



100
101
102
103
104
105
106
# File 'lib/discordrb/bot.rb', line 100

def run(async = false)
  run_async
  return if async

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

#run_asyncObject



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

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.



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

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.



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

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.



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

def server(id)
  @servers[id]
end

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



430
431
432
# File 'lib/discordrb/bot.rb', line 430

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

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



438
439
440
# File 'lib/discordrb/bot.rb', line 438

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

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



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

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

#stopObject

Kills the websocket thread, stopping all connections to Discord.



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

def stop
  @ws_thread.kill
end

#syncObject

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



137
138
139
# File 'lib/discordrb/bot.rb', line 137

def sync
  @ws_thread.join
end

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



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

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.



208
209
210
# File 'lib/discordrb/bot.rb', line 208

def user(id)
  @users[id]
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


414
415
416
# File 'lib/discordrb/bot.rb', line 414

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