Class: Discordrb::Bot
- Inherits:
-
Object
- Object
- Discordrb::Bot
- Includes:
- Cache, EventContainer
- Defined in:
- lib/discordrb/bot.rb
Overview
Represents a Discord bot, including servers, users, etc.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#event_threads ⇒ Array<Thread>
readonly
The list of currently running threads used to parse and call events.
-
#name ⇒ Object
The bot's name which discordrb sends to Discord when making any request, so Discord can identify bots with the same codebase.
-
#profile ⇒ Profile
(also: #bot_user)
readonly
The bot's user profile.
-
#servers ⇒ Hash<Integer => Server>
readonly
The list of servers the bot is currently in.
-
#shard_key ⇒ Array(Integer, Integer)
readonly
The current shard key.
-
#should_parse_self ⇒ Object
Whether or not the bot should parse its own messages.
-
#users ⇒ Hash<Integer => User>
readonly
The list of users the bot shares a server with.
-
#voices ⇒ Hash<Integer => VoiceBot>
readonly
The voice connections this bot currently has, by the server ID to which they are connected.
Instance Method Summary collapse
-
#add_await(key, type, attributes = {}) {|event| ... } ⇒ Await
Add an await the bot should listen to.
-
#create_oauth_application(name, redirect_uris) ⇒ Array(String, String)
Creates a new application to do OAuth authorization with.
-
#create_server(name, region = :london) ⇒ Server
Creates a server on Discord with a specified name and a region.
- #debug(message) ⇒ Object
-
#debug=(new_debug) ⇒ Object
Sets debug mode.
-
#delete_invite(code) ⇒ Object
Revokes an invite to a server.
-
#game=(name) ⇒ String
Sets the currently playing game to the specified game.
-
#ignore_user(user) ⇒ Object
Add a user to the list of ignored users.
-
#ignored?(user) ⇒ true, false
Checks whether a user is being ignored.
-
#initialize(email: nil, password: nil, log_mode: :normal, token: nil, application_id: nil, type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false, shard_id: nil, num_shards: nil) ⇒ Bot
constructor
Makes a new bot with the given authentication data.
-
#inject_reconnect(url) ⇒ Object
Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL.
-
#inject_resume(seq) ⇒ Object
Injects a resume packet (op 6) into the gateway.
-
#invite_url(server = nil) ⇒ String
Creates an OAuth invite URL that can be used to invite this bot to a particular server.
-
#join(invite) ⇒ Object
Makes the bot join an invite to a server.
- #log_exception(e) ⇒ Object
-
#mode=(new_mode) ⇒ Object
Sets the logging mode.
-
#parse_mention(mention) ⇒ User
Gets the user from a mention of the user.
-
#raw_token ⇒ Object
The raw token, without any prefix.
-
#run(async = false) ⇒ Object
Runs the bot, which logs into Discord and connects the WebSocket.
-
#run_async ⇒ Object
Runs the bot asynchronously.
-
#send_file(channel_id, file) ⇒ Object
Sends a file to a channel.
-
#send_message(channel_id, content, tts = false, server_id = nil) ⇒ Message
Sends a text message to a channel given its ID and the message's content.
-
#stop ⇒ Object
Kills the websocket thread, stopping all connections to Discord.
-
#suppress_ready_debug ⇒ Object
Prevents the READY packet from being printed regardless of debug mode.
-
#sync ⇒ Object
Prevents all further execution until the websocket thread stops (e. g. through a closed connection).
-
#token ⇒ String
The Discord API token received when logging in.
-
#unignore_user(user) ⇒ Object
Remove a user from the ignore list.
-
#update_oauth_application(name, redirect_uris, description = '', icon = nil) ⇒ Object
Changes information about your OAuth application.
-
#voice(thing) ⇒ VoiceBot?
Gets the voice bot for a particular server or channel.
-
#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.
-
#voice_destroy(server_id, destroy_vws = true) ⇒ Object
Disconnects the client from a specific voice connection given the server ID.
Methods included from Cache
#channel, #ensure_channel, #ensure_server, #ensure_user, #find_channel, #find_user, #init_cache, #invite, #member, #private_channel, #request_chunks, #resolve_invite_code, #server, #user
Methods included from EventContainer
#add_handler, #await, #channel_create, #channel_delete, #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, #ready, #remove_handler, #server_create, #server_delete, #server_update, #typing, #user_ban, #user_unban, #voice_state_update
Methods included from Events
Constructor Details
#initialize(email: nil, password: nil, log_mode: :normal, token: nil, application_id: nil, type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false, shard_id: nil, num_shards: nil) ⇒ 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.
Depending on the authentication information present, discordrb will deduce whether you're running on a user or a bot account. (Discord recommends using bot accounts whenever possible.) The following sets of authentication information are valid:
- token + application_id (bot account)
- email + password (user account)
- email + password + token (user account; the given token will be used for authentication instead of email and password)
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.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/discordrb/bot.rb', line 155 def initialize( email: nil, password: nil, log_mode: :normal, token: nil, application_id: nil, type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false, shard_id: nil, num_shards: nil) # Make sure people replace the login details in the example files... if email.is_a?(String) && email.end_with?('example.com') puts 'You have to replace the login details in the example files with your own!' exit end LOGGER.mode = if log_mode.is_a? TrueClass # Specifically check for `true` because people might not have updated yet :debug else log_mode end @should_parse_self = parse_self @email = email @password = password @application_id = application_id @type = determine_account_type(type, email, password, token, application_id) @name = name @shard_key = num_shards ? [shard_id, num_shards] : nil LOGGER.fancy = fancy_log @prevent_ready = suppress_ready debug('Creating token cache') token_cache = Discordrb::TokenCache.new debug('Token cache created successfully') @token = login(type, email, password, token, token_cache) init_cache @voices = {} @should_connect_to_voice = {} @ignored_ids = Set.new @event_threads = [] @current_thread = 0 end |
Instance Attribute Details
#event_threads ⇒ Array<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.
97 98 99 |
# File 'lib/discordrb/bot.rb', line 97 def event_threads @event_threads end |
#name ⇒ Object
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.
110 111 112 |
# File 'lib/discordrb/bot.rb', line 110 def name @name end |
#profile ⇒ Profile (readonly) 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=).
102 103 104 |
# File 'lib/discordrb/bot.rb', line 102 def profile @profile end |
#servers ⇒ Hash<Integer => Server> (readonly)
The list of servers the bot is currently in.
90 91 92 |
# File 'lib/discordrb/bot.rb', line 90 def servers @servers end |
#shard_key ⇒ Array(Integer, Integer) (readonly)
Returns the current shard key.
113 114 115 |
# File 'lib/discordrb/bot.rb', line 113 def shard_key @shard_key end |
#should_parse_self ⇒ Object
Whether or not the bot should parse its own messages. Off by default.
106 107 108 |
# File 'lib/discordrb/bot.rb', line 106 def should_parse_self @should_parse_self end |
#users ⇒ Hash<Integer => User> (readonly)
The list of users the bot shares a server with.
86 87 88 |
# File 'lib/discordrb/bot.rb', line 86 def users @users end |
#voices ⇒ Hash<Integer => VoiceBot> (readonly)
Returns the voice connections this bot currently has, by the server ID to which they are connected.
307 308 309 |
# File 'lib/discordrb/bot.rb', line 307 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.
536 537 538 539 540 541 |
# File 'lib/discordrb/bot.rb', line 536 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 |
#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
450 451 452 453 |
# File 'lib/discordrb/bot.rb', line 450 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 = :london) ⇒ Server
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.
436 437 438 439 440 441 442 443 |
# File 'lib/discordrb/bot.rb', line 436 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) ⇒ Object
565 566 567 |
# File 'lib/discordrb/bot.rb', line 565 def debug() LOGGER.debug() end |
#debug=(new_debug) ⇒ Object
Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
514 515 516 |
# File 'lib/discordrb/bot.rb', line 514 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.
393 394 395 396 |
# File 'lib/discordrb/bot.rb', line 393 def delete_invite(code) invite = resolve_invite_code(code) API.delete_invite(token, invite) end |
#game=(name) ⇒ String
Sets the currently playing game to the specified game.
477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'lib/discordrb/bot.rb', line 477 def game=(name) @game = name data = { op: Opcodes::PRESENCE, d: { idle_since: nil, game: name ? { name: name } : nil } } @ws.send(data.to_json) name end |
#ignore_user(user) ⇒ Object
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.
547 548 549 |
# File 'lib/discordrb/bot.rb', line 547 def ignore_user(user) @ignored_ids << user.resolve_id end |
#ignored?(user) ⇒ true, false
Checks whether a user is being ignored.
560 561 562 |
# File 'lib/discordrb/bot.rb', line 560 def ignored?(user) @ignored_ids.include?(user.resolve_id) end |
#inject_reconnect(url) ⇒ Object
Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL. If the URL is set to nil, it will reconnect and get an entirely new gateway URL. This method has not much use outside of testing and implementing highly custom reconnect logic.
496 497 498 499 500 501 502 503 |
# File 'lib/discordrb/bot.rb', line 496 def inject_reconnect(url) ({ op: Opcodes::RECONNECT, d: { url: url } }.to_json) end |
#inject_resume(seq) ⇒ Object
Injects a resume packet (op 6) into the gateway. If this is done with a running connection, it will cause an error. It has no use outside of testing stuff that I know of, but if you want to use it anyway for some reason, here it is.
509 510 511 |
# File 'lib/discordrb/bot.rb', line 509 def inject_resume(seq) resume(seq || @sequence, raw_token, @session_id) end |
#invite_url(server = nil) ⇒ String
Creates an OAuth invite URL that can be used to invite this bot to a particular server. Requires the application ID to have been set during initialization.
299 300 301 302 303 304 |
# File 'lib/discordrb/bot.rb', line 299 def invite_url(server = nil) raise 'No application ID has been set during initialization! Add one as the `application_id` named parameter while creating your bot.' unless @application_id guild_id_str = server ? "&guild_id=#{server.id}" : '' "https://discordapp.com/oauth2/authorize?&client_id=#{@application_id}#{guild_id_str}&scope=bot" end |
#join(invite) ⇒ Object
Makes the bot join an invite to a server.
290 291 292 293 |
# File 'lib/discordrb/bot.rb', line 290 def join(invite) resolved = invite(invite).code API.join_server(token, resolved) end |
#log_exception(e) ⇒ Object
570 571 572 |
# File 'lib/discordrb/bot.rb', line 570 def log_exception(e) LOGGER.log_exception(e) end |
#mode=(new_mode) ⇒ Object
Sets the logging mode
520 521 522 |
# File 'lib/discordrb/bot.rb', line 520 def mode=(new_mode) LOGGER.mode = new_mode end |
#parse_mention(mention) ⇒ User
Gets the user from a mention of the user.
468 469 470 471 472 |
# File 'lib/discordrb/bot.rb', line 468 def parse_mention(mention) # Mention format: <@id> return nil unless /<@!?(?<id>\d+)>?/ =~ mention user(id.to_i) end |
#raw_token ⇒ Object
Returns the raw token, without any prefix.
214 215 216 |
# File 'lib/discordrb/bot.rb', line 214 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
.
224 225 226 227 228 229 230 |
# File 'lib/discordrb/bot.rb', line 224 def run(async = false) run_async return if async debug('Oh wait! Not exiting yet as run was run synchronously.') sync end |
#run_async ⇒ Object
Runs the bot asynchronously. Equivalent to #run with the :async parameter.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/discordrb/bot.rb', line 234 def run_async # Handle heartbeats @heartbeat_interval = 1 @heartbeat_active = false @heartbeat_thread = Thread.new do Thread.current[:discordrb_name] = 'heartbeat' loop do if @heartbeat_active send_heartbeat sleep @heartbeat_interval else sleep 1 end end end @ws_thread = Thread.new do Thread.current[:discordrb_name] = 'websocket' # Initialize falloff so we wait for more time before reconnecting each time @falloff = 1.0 loop do websocket_connect if @reconnect_url # We got an op 7! Don't wait before reconnecting LOGGER.info('Got an op 7, reconnecting right away') else wait_for_reconnect end # Restart the loop, i. e. reconnect end LOGGER.warn('The WS loop exited! Not sure if this is a good thing') 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
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.
415 416 417 418 |
# File 'lib/discordrb/bot.rb', line 415 def send_file(channel_id, file) response = API.send_file(token, channel_id, file) Message.new(JSON.parse(response), self) end |
#send_message(channel_id, content, tts = false, server_id = nil) ⇒ Message
Sends a text message to a channel given its ID and the message's content.
403 404 405 406 407 408 409 |
# File 'lib/discordrb/bot.rb', line 403 def (channel_id, content, tts = false, server_id = nil) channel_id = channel_id.resolve_id debug("Sending message to #{channel_id} with content '#{content}'") response = API.(token, channel_id, content, [], tts, server_id) Message.new(JSON.parse(response), self) end |
#stop ⇒ Object
Kills the websocket thread, stopping all connections to Discord.
284 285 286 |
# File 'lib/discordrb/bot.rb', line 284 def stop @ws_thread.kill end |
#suppress_ready_debug ⇒ Object
Prevents the READY packet from being printed regardless of debug mode.
525 526 527 |
# File 'lib/discordrb/bot.rb', line 525 def suppress_ready_debug @prevent_ready = true end |
#sync ⇒ Object
Prevents all further execution until the websocket thread stops (e. g. through a closed connection).
279 280 281 |
# File 'lib/discordrb/bot.rb', line 279 def sync @ws_thread.join end |
#token ⇒ String
The Discord API token received when logging in. Useful to explicitly call API methods.
207 208 209 210 |
# File 'lib/discordrb/bot.rb', line 207 def token API.bot_name = @name @token end |
#unignore_user(user) ⇒ Object
Remove a user from the ignore list.
553 554 555 |
# File 'lib/discordrb/bot.rb', line 553 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
461 462 463 |
# File 'lib/discordrb/bot.rb', line 461 def update_oauth_application(name, redirect_uris, description = '', icon = nil) API.update_oauth_application(@token, name, redirect_uris, description, icon) end |
#voice(thing) ⇒ VoiceBot?
Gets the voice bot for a particular server or channel. You can connect to a new channel using the #voice_connect method.
313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/discordrb/bot.rb', line 313 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 VoiceBot#destroy before calling this method.
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/discordrb/bot.rb', line 334 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}") data = { op: Opcodes::VOICE_STATE, d: { guild_id: server_id.to_s, channel_id: chan.id.to_s, self_mute: false, self_deaf: false } } debug("Voice channel init packet is: #{data.to_json}") @should_connect_to_voice[server_id] = chan @ws.send(data.to_json) 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_id, 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.
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/discordrb/bot.rb', line 372 def voice_destroy(server_id, destroy_vws = true) data = { op: Opcodes::VOICE_STATE, d: { guild_id: server_id.to_s, channel_id: nil, self_mute: false, self_deaf: false } } debug("Voice channel destroy packet is: #{data.to_json}") @ws.send(data.to_json) @voices[server_id].destroy if @voices[server_id] && destroy_vws @voices.delete(server_id) end |