Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: application emojis #282

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions lib/discordrb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ def self.timestamp(time, style = nil)
end
end

# A utility method to base64 enocde a file like object using its mime type.
# @param file [File, #read] A file like object that responds to #read.
# @return [String] The base64 encoded file object as image data.
def encode_file(file)
path_method = %i[original_filename path local_path].find { |meth| file.respond_to?(meth) }

raise ArgumentError, 'File object must respond to original_filename, path, or local path.' unless path_method
raise ArgumentError, 'File object must respond to read.' unless file.respond_to?(:read)

mime = MIME::Types.type_for(file.__send__(path_method)).first&.to_s || 'image/jpeg'
"data:#{mime};base64,#{Base64.encode64(file.read).strip}"
end

# In discordrb, Integer and {String} are monkey-patched to allow for easy resolution of IDs
class Integer
# @return [Integer] The Discord ID represented by this integer, i.e. the integer itself
Expand Down
51 changes: 0 additions & 51 deletions lib/discordrb/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,30 +231,6 @@ def role_icon_url(role_id, icon_hash, format = 'webp')
"#{cdn_url}/role-icons/#{role_id}/#{icon_hash}.#{format}"
end

# Login to the server
def login(email, password)
request(
:auth_login,
nil,
:post,
"#{api_base}/auth/login",
email: email,
password: password
)
end

# Logout from the server
def logout(token)
request(
:auth_logout,
nil,
:post,
"#{api_base}/auth/logout",
nil,
Authorization: token
)
end

# Create an OAuth application
def create_oauth_application(token, name, redirect_uris)
request(
Expand Down Expand Up @@ -292,20 +268,6 @@ def oauth_application(token)
)
end

# Acknowledge that a message has been received
# The last acknowledged message will be sent in the ready packet,
# so this is an easy way to catch up on messages
def acknowledge_message(token, channel_id, message_id)
request(
:channels_cid_messages_mid_ack,
nil, # This endpoint is unavailable for bot accounts and thus isn't subject to its rate limit requirements.
:post,
"#{api_base}/channels/#{channel_id}/messages/#{message_id}/ack",
nil,
Authorization: token
)
end

# Get the gateway to be used
def gateway(token)
request(
Expand All @@ -329,19 +291,6 @@ def gateway_bot(token)
)
end

# Validate a token (this request will fail if the token is invalid)
def validate_token(token)
request(
:auth_login,
nil,
:post,
"#{api_base}/auth/login",
{}.to_json,
Authorization: token,
content_type: :json
)
end

# Get a list of available voice regions
def voice_regions(token)
request(
Expand Down
64 changes: 64 additions & 0 deletions lib/discordrb/api/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,68 @@ def batch_edit_command_permissions(token, application_id, guild_id, permissions)
content_type: :json
)
end

# Get a list of application emojis.
# https://discord.com/developers/docs/resources/emoji#list-application-emojis
def list_application_emojis(token, application_id)
Discordrb::API.request(
:applications_aid_emojis,
application_id,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/emojis",
Authorization: token
)
end

# Get an application emoji by ID.
# https://discord.com/developers/docs/resources/emoji#get-application-emoji
def get_application_emoji(token, application_id, emoji_id)
Discordrb::API.request(
:applications_aid_emojis_eid,
application_id,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}",
Authorization: token
)
end

# Create an application emoji.
# https://discord.com/developers/docs/resources/emoji#create-application-emoji
def create_application_emoji(token, application_id, name, image)
Discordrb::API.request(
:applications_aid_emojis,
application_id,
:post,
"#{Discordrb::API.api_base}/applications/#{application_id}/emojis",
{ name: name, image: image }.to_json,
Authorization: token,
content_type: :json
)
end

# Edit an application emoji.
# https://discord.com/developers/docs/resources/emoji#modify-application-emoji
def edit_application_emoji(token, application_id, emoji_id, name)
Discordrb::API.request(
:applications_aid_emojis_eid,
application_id,
:patch,
"#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}",
{ name: name }.to_json,
Authorization: token,
content_type: :json
)
end

# Delete an application emoji.
# https://discord.com/developers/docs/resources/emoji#delete-application-emoji
def delete_application_emoji(token, application_id, emoji_id)
Discordrb::API.request(
:applications_aid_emojis_eid,
application_id,
:delete,
"#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}",
Authorization: token
)
end
end
12 changes: 3 additions & 9 deletions lib/discordrb/api/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ def roles(token, server_id)
# sending TTS messages, embedding links, sending files, reading the history, mentioning everybody,
# connecting to voice, speaking and voice activity (push-to-talk isn't mandatory)
# https://discord.com/developers/docs/resources/guild#get-guild-roles
def create_role(token, server_id, name, colour, hoist, mentionable, packed_permissions, reason = nil)
def create_role(token, server_id, name, colour, hoist, mentionable, packed_permissions, icon, reason = nil)
Discordrb::API.request(
:guilds_sid_roles,
server_id,
:post,
"#{Discordrb::API.api_base}/guilds/#{server_id}/roles",
{ color: colour, name: name, hoist: hoist, mentionable: mentionable, permissions: packed_permissions }.to_json,
{ color: colour, name: name, hoist: hoist, mentionable: mentionable, permissions: packed_permissions, icon: icon }.to_json,
Authorization: token,
content_type: :json,
'X-Audit-Log-Reason': reason
Expand All @@ -267,13 +267,7 @@ def update_role(token, server_id, role_id, name, colour, hoist = false, mentiona
data = { color: colour, name: name, hoist: hoist, mentionable: mentionable, permissions: packed_permissions }

if icon != :undef && icon
path_method = %i[original_filename path local_path].find { |meth| icon.respond_to?(meth) }

raise ArgumentError, 'File object must respond to original_filename, path, or local path.' unless path_method
raise ArgumentError, 'File must respond to read' unless icon.respond_to? :read

mime_type = MIME::Types.type_for(icon.__send__(path_method)).first&.to_s || 'image/jpeg'
data[:icon] = "data:#{mime_type};base64,#{Base64.encode64(icon.read).strip}"
data[:icon] = encode_file(icon)
elsif icon.nil?
data[:icon] = nil
end
Expand Down
29 changes: 13 additions & 16 deletions lib/discordrb/api/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def change_own_nickname(token, server_id, nick, reason = nil)
:guilds_sid_members_me_nick,
server_id, # This is technically a guild endpoint
:patch,
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/@me/nick",
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/@me",
{ nick: nick }.to_json,
Authorization: token,
content_type: :json,
Expand All @@ -45,13 +45,13 @@ def change_own_nickname(token, server_id, nick, reason = nil)

# Update user data
# https://discord.com/developers/docs/resources/user#modify-current-user
def update_profile(token, email, password, new_username, avatar, new_password = nil)
def update_profile(token, username = :undef, avatar = :undef, banner = :undef)
Discordrb::API.request(
:users_me,
nil,
:patch,
"#{Discordrb::API.api_base}/users/@me",
{ avatar: avatar, email: email, new_password: new_password, password: password, username: new_username }.to_json,
{ username: username, avatar: avatar, banner: banner }.reject { |_, v| v == :undef }.to_json,
Authorization: token,
content_type: :json
)
Expand Down Expand Up @@ -119,19 +119,6 @@ def connections(token)
)
end

# Change user status setting
def change_status_setting(token, status)
Discordrb::API.request(
:users_me_settings,
nil,
:patch,
"#{Discordrb::API.api_base}/users/@me/settings",
{ status: status }.to_json,
Authorization: token,
content_type: :json
)
end

# Returns one of the "default" discord avatars from the CDN given a discriminator or id since new usernames
# TODO: Maybe change this method again after discriminator removal ?
def default_avatar(discrim_id = 0, legacy: false)
Expand All @@ -152,4 +139,14 @@ def avatar_url(user_id, avatar_id, format = nil)
end
"#{Discordrb::API.cdn_url}/avatars/#{user_id}/#{avatar_id}.#{format}"
end

# Make a banner URL from the user and banner IDs
def banner_url(user_id, banner_id, format = nil)
format ||= if banner_id.start_with?('a_')
'gif'
else
'png'
end
"#{Discordrb::API.cdn_url}/banners/#{user_id}/#{banner_id}.#{format}"
end
end
43 changes: 42 additions & 1 deletion lib/discordrb/bot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ def parse_mentions(mentions, server = nil)
end
end
elsif /(?<animated>^a|^${0}):(?<name>\w+):(?<id>\d+)/ =~ mention
array_to_return << (emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil))
array_to_return << (emoji(id) || Emoji.new({ 'animated' => animated != '', 'name' => name, 'id' => id }, self, nil))
end
end
array_to_return
Expand Down Expand Up @@ -904,6 +904,46 @@ def edit_application_command_permissions(command_id, server_id, permissions = []
API::Application.edit_guild_command_permissions(@token, profile.id, server_id, command_id, permissions)
end

# Fetches all the application emojis that the bot can use.
# @return [Array<Emoji>] Returns an array of emoji objects.
def application_emojis
response = API::Application.list_application_emojis(@token, profile.id)
JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) }
end

# Fetches a single application emoji from its ID.
# @param emoji_id [Integer, String] ID of the application emoji.
# @return [Emoji] The application emoji.
def get_application_emoji(emoji_id)
response = API::Application.get_application_emoji(@token, profile.id, emoji_id)
Emoji.new(JSON.parse(response), self)
end

# Creates a new custom emoji that can be used by this application.
# @param name [String] The name of emoji to create.
# @param image [String, #read] Base64 string with the image data, or an object that responds to #read.
# @return [Emoji] The emoji that has been created.
def create_application_emoji(name, image)
image = image.respond_to?(:read) ? encode_file(image) : image
response = API::Application.create_application_emoji(@token, profile.id, name, image)
Emoji.new(JSON.parse(response), self)
end

# Edits an existing application emoji.
# @param emoji_id [Integer, String] ID of the application emoji to edit.
# @param name [String] The new name of the emoji.
# @return [Emoji] Returns the updated emoji object on success.
def edit_application_emoji(emoji_id, name)
response = API::Application.edit_application_emoji(@token, profile.id, emoji_id, name)
Emoji.new(JSON.parse(response), self)
end

# Deletes an existing application emoji.
# @param emoji_id [Integer, String] ID of the application emoji to delete.
def delete_application_emoji(emoji_id)
API::Application.delete_application_emoji(@token, profile.id, emoji_id)
end

private

# Throws a useful exception if there's currently no gateway connection.
Expand Down Expand Up @@ -1714,6 +1754,7 @@ def handle_awaits(event)
end

def calculate_intents(intents)
intents = [intents] unless intents.is_a? Array
intents.reduce(0) do |sum, intent|
case intent
when Symbol
Expand Down
2 changes: 1 addition & 1 deletion lib/discordrb/data/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ def bulk_delete(ids, strict = false, reason = nil)
true
end

API::Channel.bulk_delete_messages(@bot.token, @id, ids, reason)
API::Channel.bulk_delete_messages(@bot.token, @id, ids, reason) unless ids.size < 2
ids.size
end

Expand Down
20 changes: 20 additions & 0 deletions lib/discordrb/data/emoji.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,25 @@ class Emoji
# @return [Server, nil] the server of this emoji
attr_reader :server

# @return [User, nil] the user who uploaded this emoji, or nil if the emoji's server is unknown
attr_reader :user

# @return [Array<Role>, nil] roles this emoji is active for, or nil if the emoji's server is unknown
attr_reader :roles

# @return [Boolean, nil] if the emoji requires colons to be used, or nil if the emoji's server is unknown
attr_reader :require_colons
alias_method :require_colons?, :require_colons

# @return [Boolean, nil] whether this emoji is managed by an integration, or nil if the emoji's server is unknown
attr_reader :managed
alias_method :managed?, :managed

# @return [Boolean, nil] if this emoji is currently usable, or nil if the emoji's server is unknown
attr_reader :available
alias_method :available?, :available
alias_method :usable?, :available

# @return [true, false] if the emoji is animated
attr_reader :animated
alias_method :animated?, :animated
Expand All @@ -27,6 +43,10 @@ def initialize(data, bot, server = nil)
@server = server
@id = data['id']&.to_i
@animated = data['animated']
@managed = data['managed']
@available = data['available']
@require_colons = data['require_colons']
@user = data['user'] ? bot.ensure_user(data['user']) : nil

process_roles(data['roles']) if server
end
Expand Down
Loading
Loading