class MijDiscord::Bot
Constants
- EVENTS
- UNAVAILABLE_SERVER_TIMEOUT
Attributes
auth[R]
cache[R]
gateway[R]
profile[R]
shard_key[R]
Public Class Methods
new(client_id:, token:, type: :bot, name: nil, shard_id: nil, num_shards: nil, ignore_bots: false, ignore_self: true)
click to toggle source
# File lib/mij-discord/bot.rb, line 97 def initialize(client_id:, token:, type: :bot, name: nil, shard_id: nil, num_shards: nil, ignore_bots: false, ignore_self: true) @auth = AuthInfo.new(client_id, token, type, name) @cache = MijDiscord::Cache::BotCache.new(self) @shard_key = [shard_id, num_shards] if num_shards @gateway = MijDiscord::Core::Gateway.new(self, @auth, @shard_key) @ignore_bots, @ignore_self, @ignored_ids = ignore_bots, ignore_self, Set.new @unavailable_servers = 0 @event_dispatchers = {} end
Public Instance Methods
accept_invite(invite)
click to toggle source
# File lib/mij-discord/bot.rb, line 215 def accept_invite(invite) code = parse_invite_code(invite) MijDiscord::Core::API::Invite.accept(@auth, code) nil end
add_event(type, key = nil, **filter, &block)
click to toggle source
# File lib/mij-discord/bot.rb, line 312 def add_event(type, key = nil, **filter, &block) raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type] event = (@event_dispatchers[type] ||= MijDiscord::Events::EventDispatcher.new(EVENTS[type], self)) event.add_callback(key, filter, &block) end
application()
click to toggle source
# File lib/mij-discord/bot.rb, line 202 def application raise 'Cannot get OAuth application for non-bot user' unless @auth.bot? response = MijDiscord::Core::API.oauth_application(@auth) MijDiscord::Data::Application.new(JSON.parse(response), self) end
channel(id, server = nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 150 def channel(id, server = nil) gateway_check @cache.get_channel(id, server) end
channels()
click to toggle source
# File lib/mij-discord/bot.rb, line 145 def channels gateway_check @cache.list_channels end
connect(async = true)
click to toggle source
# File lib/mij-discord/bot.rb, line 113 def connect(async = true) @gateway.run_async @gateway.sync unless async nil end
connected?()
click to toggle source
# File lib/mij-discord/bot.rb, line 131 def connected? @gateway.open? end
create_server(name, region = 'eu-central')
click to toggle source
# File lib/mij-discord/bot.rb, line 228 def create_server(name, region = 'eu-central') response = API::Server.create(@auth, name, region) id = JSON.parse(response)['id'].to_i loop do server = @cache.get_server(id, local: true) return server if server sleep(0.1) end end
disconnect(no_sync = false)
click to toggle source
# File lib/mij-discord/bot.rb, line 124 def disconnect(no_sync = false) @gateway.stop(no_sync) nil end
Also aliased as: shutdown
emoji(server_id, id)
click to toggle source
# File lib/mij-discord/bot.rb, line 197 def emoji(server_id, id) gateway_check server(server_id)&.emoji(id) end
emojis(server_id)
click to toggle source
# File lib/mij-discord/bot.rb, line 192 def emojis(server_id) gateway_check server(server_id)&.emojis end
events(type)
click to toggle source
# File lib/mij-discord/bot.rb, line 326 def events(type) raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type] @event_dispatchers[type]&.callbacks || [] end
handle_dispatch(type, data)
click to toggle source
# File lib/mij-discord/bot.rb, line 388 def handle_dispatch(type, data) MijDiscord::LOGGER.debug('Dispatch') { "<#{type} #{data.inspect}>" } if @unavailable_servers > 0 && Time.now > @unavailable_servers_timeout MijDiscord::LOGGER.warn('Dispatch') { "Proceeding with #{@unavailable_servers} servers still unavailable" } @unavailable_servers = 0 notify_ready end case type when :CONNECT trigger_event(:connect, self) when :DISCONNECT trigger_event(:disconnect, self) when :READY @cache.reset @profile = MijDiscord::Data::Profile.new(data['user'], self) @profile.update_presence('status' => :online) @unavailable_servers = 0 @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT data['guilds'].each do |sv| if sv['unavailable'].eql?(true) @unavailable_servers += 1 else @cache.put_server(sv) end end data['private_channels'].each do |ch| @cache.put_channel(ch, nil) end notify_ready if @unavailable_servers.zero? when :SESSIONS_REPLACE # Do nothing with session replace because no idea what it does. when :PRESENCES_REPLACE # Do nothing with presences replace because no idea what it does. when :GUILD_MEMBERS_CHUNK server = @cache.get_server(data['guild_id']) server.update_members_chunk(data['members']) when :GUILD_CREATE server = @cache.put_server(data) if data['unavailable'].eql?(false) @unavailable_servers -= 1 @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT notify_ready if @unavailable_servers.zero? return end trigger_event(:create_server, self, server) when :GUILD_SYNC server = @cache.get_server(data['id']) server.update_synced_data(data) when :GUILD_UPDATE server = @cache.put_server(data, update: true) trigger_event(:update_server, self, server) when :GUILD_DELETE server = @cache.remove_server(data['id']) if data['unavailable'].eql?(true) MijDiscord::LOGGER.warn('Dispatch') { "Server <#{data['id']}> died due to outage" } return end trigger_event(:delete_server, self, server) when :CHANNEL_CREATE channel = @cache.put_channel(data, nil) trigger_event(:create_channel, self, channel) when :CHANNEL_UPDATE channel = @cache.put_channel(data, nil, update: true) trigger_event(:update_channel, self, channel) when :CHANNEL_DELETE channel = @cache.remove_channel(data['id']) trigger_event(:delete_channel, self, channel) when :WEBHOOKS_UPDATE channel = @cache.get_channel(data['channel_id'], nil) trigger_event(:update_webhooks, self, channel) when :CHANNEL_PINS_UPDATE channel = @cache.get_channel(data['channel_id'], nil) trigger_event(:update_pins, self, channel) when :CHANNEL_PINS_ACK # Do nothing with pins acknowledgement when :CHANNEL_RECIPIENT_ADD channel = @cache.get_channel(data['channel_id'], nil) recipient = channel.update_recipient(add: data['user']) trigger_event(:add_recipient, self, channel, recipient) when :CHANNEL_RECIPIENT_REMOVE channel = @cache.get_channel(data['channel_id'], nil) recipient = channel.update_recipient(remove: data['user']) trigger_event(:remove_recipient, self, channel, recipient) when :GUILD_MEMBER_ADD server = @cache.get_server(data['guild_id']) member = server.update_member(data, :add) trigger_event(:create_member, self, member, server) when :GUILD_MEMBER_UPDATE server = @cache.get_server(data['guild_id']) member = server.update_member(data, :update) trigger_event(:update_member, self, member, server) when :GUILD_MEMBER_REMOVE server = @cache.get_server(data['guild_id']) member = server.update_member(data, :remove) trigger_event(:delete_member, self, member, server) when :GUILD_ROLE_CREATE server = @cache.get_server(data['guild_id']) role = server.cache.put_role(data['role']) trigger_event(:create_role, self, server, role) when :GUILD_ROLE_UPDATE server = @cache.get_server(data['guild_id']) role = server.cache.put_role(data['role'], update: true) trigger_event(:update_role, self, server, role) when :GUILD_ROLE_DELETE server = @cache.get_server(data['guild_id']) role = server.cache.remove_role(data['role_id']) trigger_event(:delete_role, self, server, role) when :GUILD_EMOJIS_UPDATE server = @cache.get_server(data['guild_id']) old_emojis = server.emojis server.update_emojis(data) trigger_event(:update_emoji, self, server, old_emojis, server.emojis) when :GUILD_BAN_ADD server = @cache.get_server(data['guild_id']) user = @cache.get_user(data['user']['id'], local: @auth.user?) user ||= MijDiscord::Data::User.new(data['user'], self) trigger_event(:ban_user, self, server, user) when :GUILD_BAN_REMOVE server = @cache.get_server(data['guild_id']) user = @cache.get_user(data['user']['id'], local: @auth.user?) user ||= MijDiscord::Data::User.new(data['user'], self) trigger_event(:unban_user, self, server, user) when :MESSAGE_CREATE channel = @cache.get_channel(data['channel_id'], nil) message = channel.cache.put_message(data) return if ignored_user?(data['author']['id']) trigger_event(:create_message, self, message) if message.channel.private? trigger_event(:private_message, self, message) else trigger_event(:channel_message, self, message) end when :MESSAGE_ACK # Do nothing with message acknowledgement when :MESSAGE_UPDATE author = data['author'] return if author.nil? channel = @cache.get_channel(data['channel_id'], nil) message = channel.cache.put_message(data, update: true) return if ignored_user?(author['id']) trigger_event(:edit_message, self, message) when :MESSAGE_DELETE channel = @cache.get_channel(data['channel_id'], nil) channel.cache.remove_message(data['id']) trigger_event(:delete_message, self, data) when :MESSAGE_DELETE_BULK messages = data['ids'].map {|x| {'id' => x, 'channel_id' => data['channel_id']} } messages.each {|x| trigger_event(:delete_message, self, x) } when :MESSAGE_REACTION_ADD channel = @cache.get_channel(data['channel_id'], nil) message = channel.cache.get_message(data['message_id'], local: true) message.update_reaction(add: data) if message return if ignored_user?(data['user_id']) trigger_event(:add_reaction, self, data) trigger_event(:toggle_reaction, self, data) when :MESSAGE_REACTION_REMOVE channel = @cache.get_channel(data['channel_id'], nil) message = channel.cache.get_message(data['message_id'], local: true) message.update_reaction(remove: data) if message return if ignored_user?(data['user_id']) trigger_event(:remove_reaction, self, data) trigger_event(:toggle_reaction, self, data) when :MESSAGE_REACTION_REMOVE_ALL channel = @cache.get_channel(data['channel_id'], nil) message = channel.cache.get_message(data['message_id'], local: true) message.update_reaction(clear: true) if message trigger_event(:clear_reactions, self, data) when :TYPING_START begin return if ignored_user?(data['user_id']) trigger_event(:start_typing, self, data) rescue MijDiscord::Errors::Forbidden # Ignoring the channel we can't access # Why is this even sent? :S end when :USER_UPDATE user = @cache.put_user(data, update: true) @profile.update_data(data) if @profile == user trigger_event(:update_user, self, user) when :PRESENCE_UPDATE # Bullshit logic due to Discord gateway sending this in a stupid way # TODO: Rewrite relevant parts for better handling? if data['guild_id'] server = @cache.get_server(data['guild_id']) user = server.cache.get_member(data['user']['id']) user&.update_presence(data) user&.update_data(data) else user = @cache.get_user(data['user']['id']) user&.update_presence(data) user&.update_data(data['user']) if @profile == user @profile.update_presence(data) @profile.update_data(data['user']) end end trigger_event(:update_presence, self, data) when :VOICE_STATE_UPDATE server = @cache.get_server(data['guild_id']) state = server.update_voice_state(data) trigger_event(:update_voice_state, self, state) else MijDiscord::LOGGER.warn('Dispatch') { "Unhandled gateway event type: #{type}" } trigger_event(:unhandled, self, type, data) end rescue => exc MijDiscord::LOGGER.error('Dispatch') { 'An error occurred in dispatch handler' } MijDiscord::LOGGER.error('Dispatch') { exc } end
handle_exception(type, exception, payload = nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 382 def handle_exception(type, exception, payload = nil) return if type == :event && payload&.is_a?(MijDiscord::Events::Exception) trigger_event(:exception, self, type, exception, payload) end
handle_heartbeat()
click to toggle source
# File lib/mij-discord/bot.rb, line 378 def handle_heartbeat trigger_event(:heartbeat, self) end
ignore_user(user)
click to toggle source
# File lib/mij-discord/bot.rb, line 332 def ignore_user(user) @ignored_ids << user.to_id nil end
ignored_user?(user)
click to toggle source
# File lib/mij-discord/bot.rb, line 342 def ignored_user?(user) user = user.to_id return true if @ignore_self && user == @auth.id return true if @ignored_ids.include?(user) if @ignore_bots && (user = @cache.get_user(user, local: true)) return true if user.bot_account? end false end
inspect()
click to toggle source
# File lib/mij-discord/bot.rb, line 663 def inspect MijDiscord.make_inspect(self, :auth) end
invite(invite)
click to toggle source
# File lib/mij-discord/bot.rb, line 209 def invite(invite) code = parse_invite_code(invite) response = MijDiscord::Core::API::Invite.resolve(@auth, code, true) MijDiscord::Data::Invite.new(JSON.parse(response), self) end
make_invite_url(server: nil, permissions: nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 221 def make_invite_url(server: nil, permissions: nil) url = "https://discordapp.com/oauth2/authorize?scope=bot&client_id=#{@auth.id}".dup url << "&permissions=#{permissions.to_i}" if permissions.respond_to?(:to_i) url << "&guild_id=#{server.to_id}" if server.respond_to?(:to_id) url end
member(server_id, id)
click to toggle source
# File lib/mij-discord/bot.rb, line 177 def member(server_id, id) gateway_check server(server_id)&.member(id) end
members(server_id)
click to toggle source
# File lib/mij-discord/bot.rb, line 172 def members(server_id) gateway_check server(server_id)&.members end
parse_invite_code(invite)
click to toggle source
# File lib/mij-discord/bot.rb, line 240 def parse_invite_code(invite) case invite when %r[^(?:https?://)?discord\.gg/(\w+)$]i then $1 when %r[^https?://discordapp\.com/invite/(\w+)$]i then $1 when %r[^([a-zA-Z0-9]+)$] then $1 when MijDiscord::Data::Invite then invite.code else raise ArgumentError, 'Invalid invite format' end end
parse_mention(mention, server_id = nil, type: nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 282 def parse_mention(mention, server_id = nil, type: nil) gateway_check mention = mention.to_s.strip if !type.nil? && mention =~ /^(\d+)$/ parse_mention_id($1, type, server_id) elsif mention =~ /^<@!?(\d+)>$/ return nil if type && type != :user parse_mention_id($1, :user, server_id) elsif mention =~ /^<#(\d+)>$/ return nil if type && type != :channel parse_mention_id($1, :channel, server_id) elsif mention =~ /^<@&(\d+)>$/ return nil if type && type != :role parse_mention_id($1, :role, server_id) elsif mention =~ /^<(a?):(\w+):(\d+)>$/ return nil if type && type != :emoji parse_mention_id($1, :emoji, server_id) || begin em_data = { 'id' => $3.to_i, 'name' => $2, 'animated' => !$1.empty? } MijDiscord::Data::Emoji.new(em_data, nil) end end end
parse_mention_id(mention, type, server_id = nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 250 def parse_mention_id(mention, type, server_id = nil) case type when :user return server_id ? member(server_id, mention) : user(mention) when :channel return channel(mention, server_id) when :role role = role(server_id, mention) return role if role servers.each do |sv| role = sv.role(mention) return role if role end when :emoji emoji = emoji(server_id, mention) return emoji if emoji servers.each do |sv| emoji = sv.emoji(mention) return emoji if emoji end else raise TypeError, "Invalid mention type '#{type}'" end nil end
pm_channel(id)
click to toggle source
# File lib/mij-discord/bot.rb, line 155 def pm_channel(id) gateway_check @cache.get_pm_channel(id) end
Also aliased as: dm_channel
remove_event(type, key)
click to toggle source
# File lib/mij-discord/bot.rb, line 319 def remove_event(type, key) raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type] @event_dispatchers[type]&.remove_callback(key) nil end
role(server_id, id)
click to toggle source
# File lib/mij-discord/bot.rb, line 187 def role(server_id, id) gateway_check server(server_id)&.role(id) end
roles(server_id)
click to toggle source
# File lib/mij-discord/bot.rb, line 182 def roles(server_id) gateway_check server(server_id)&.roles end
server(id)
click to toggle source
# File lib/mij-discord/bot.rb, line 140 def server(id) gateway_check @cache.get_server(id) end
servers()
click to toggle source
# File lib/mij-discord/bot.rb, line 135 def servers gateway_check @cache.list_servers end
sync()
click to toggle source
# File lib/mij-discord/bot.rb, line 119 def sync @gateway.sync nil end
unignore_user(user)
click to toggle source
# File lib/mij-discord/bot.rb, line 337 def unignore_user(user) @ignored_ids.delete(user.to_id) nil end
update_presence(status: nil, game: nil)
click to toggle source
# File lib/mij-discord/bot.rb, line 355 def update_presence(status: nil, game: nil) gateway_check status = case status when nil then @profile.status when :online, :idle, :dnd, :online then status else raise ArgumentError, 'Invalid status' end game = case game when nil then @profile.game when false then nil when String, Hash MijDiscord::Data::Game.construct(game) when MijDiscord::Data::Game then game else raise ArgumentError, 'Invalid game' end&.to_hash @gateway.send_status_update(status, nil, game, false) @profile.update_presence('status' => status, 'game' => game) nil end
user(id)
click to toggle source
# File lib/mij-discord/bot.rb, line 167 def user(id) gateway_check @cache.get_user(id) end
users()
click to toggle source
# File lib/mij-discord/bot.rb, line 162 def users gateway_check @cache.list_users end
Private Instance Methods
gateway_check()
click to toggle source
# File lib/mij-discord/bot.rb, line 669 def gateway_check raise 'A gateway connection is required for this action' unless connected? end
notify_ready()
click to toggle source
# File lib/mij-discord/bot.rb, line 673 def notify_ready @gateway.notify_ready trigger_event(:ready, self) if @auth.user? guilds = @cache.list_servers.map(&:id) @gateway.send_request_guild_sync(guilds) end end
trigger_event(name, *args)
click to toggle source
# File lib/mij-discord/bot.rb, line 684 def trigger_event(name, *args) @event_dispatchers[name]&.trigger(args) end