From ce4cef45c778505181135f8705881e336ff6bc17 Mon Sep 17 00:00:00 2001 From: DroidDevelopment Date: Fri, 25 Oct 2024 16:07:17 -0400 Subject: [PATCH 01/14] Add application emoji endpoints. --- lib/discordrb/api/application.rb | 64 ++++++++++++++++++++++++++++++++ lib/discordrb/bot.rb | 43 +++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/lib/discordrb/api/application.rb b/lib/discordrb/api/application.rb index dc5f7d50e..8b91a7d62 100644 --- a/lib/discordrb/api/application.rb +++ b/lib/discordrb/api/application.rb @@ -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, + nil, + :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, + nil, + :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, + nil, + :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, + nil, + :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, + nil, + :delete, + "#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}", + Authorization: token + ) + end end diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index 9fb43b91d..17074f39d 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -902,6 +902,49 @@ 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] Returns an array of emoji objects. + def application_emojis + response = JSON.parse(API::Application.list_application_emojis(@token, profile.id)) + response['items'].map { |emoji| Emoji.new(emoji, bot, nil) } + end + + # Fetches a single application emoji from ID. + # @return [Emoji] Returns an emoji object. + def get_application_emoji(emoji_id) + response = JSON.parse(API::Application.get_application_emoji(@token, profile.id, emoji_id)) + Emoji.new(response, bot, nil) + end + + # Adds a new custom emoji that can be used by this application. + # @param name [String] The name of emoji to create. + # @param image [String, #read] A base64 encoded string with the image data, or an object that responds to `#read`, such as `File`. + # @return [Emoji] The emoji that has been created. + def create_application_emoji(name, image) + image_string = image + if image.respond_to? :read + image_string = 'data:image/jpg;base64,' + image_string += Base64.strict_encode64(image.read) + end + + response = JSON.parse(API::Application.create_application_emoji(@token, profile.id, name, image_string)) + Emoji.new(response, bot, nil) + end + + # Edits an existing application emoji. + # @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 = JSON.parse(API::Application.edit_application_emoji(@token, profile.id, emoji_id, name)) + Emoji.new(response, bot, nil) + end + + # Deletes an existing application emoji. + # @param emoji_id [String] Snowflake ID of the 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. From c747b058ee93c4217c38be9ee7c16ab4c761e6c6 Mon Sep 17 00:00:00 2001 From: DroidDevelopment Date: Fri, 25 Oct 2024 16:20:08 -0400 Subject: [PATCH 02/14] Use bot instance variable. --- lib/discordrb/bot.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index 17074f39d..a3eb97023 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -906,14 +906,14 @@ def edit_application_command_permissions(command_id, server_id, permissions = [] # @return [Array] Returns an array of emoji objects. def application_emojis response = JSON.parse(API::Application.list_application_emojis(@token, profile.id)) - response['items'].map { |emoji| Emoji.new(emoji, bot, nil) } + response['items'].map { |emoji| Emoji.new(emoji, @bot, nil) } end # Fetches a single application emoji from ID. # @return [Emoji] Returns an emoji object. def get_application_emoji(emoji_id) response = JSON.parse(API::Application.get_application_emoji(@token, profile.id, emoji_id)) - Emoji.new(response, bot, nil) + Emoji.new(response, @bot, nil) end # Adds a new custom emoji that can be used by this application. @@ -928,7 +928,7 @@ def create_application_emoji(name, image) end response = JSON.parse(API::Application.create_application_emoji(@token, profile.id, name, image_string)) - Emoji.new(response, bot, nil) + Emoji.new(response, @bot, nil) end # Edits an existing application emoji. @@ -936,7 +936,7 @@ def create_application_emoji(name, image) # @return [Emoji] Returns the updated emoji object on success. def edit_application_emoji(emoji_id, name) response = JSON.parse(API::Application.edit_application_emoji(@token, profile.id, emoji_id, name)) - Emoji.new(response, bot, nil) + Emoji.new(response, @bot, nil) end # Deletes an existing application emoji. From 3da6ff3c8bf8d7ef8af3d3f89c7346233f091b40 Mon Sep 17 00:00:00 2001 From: DroidDevelopment Date: Wed, 30 Oct 2024 19:58:50 -0400 Subject: [PATCH 03/14] Use MIME-TYPES. --- lib/discordrb/bot.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index a3eb97023..23e367326 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -918,14 +918,16 @@ def get_application_emoji(emoji_id) # Adds a new custom emoji that can be used by this application. # @param name [String] The name of emoji to create. - # @param image [String, #read] A base64 encoded string with the image data, or an object that responds to `#read`, such as `File`. + # @param image [String, #read] An object that responds to `#read`, such as `File`. # @return [Emoji] The emoji that has been created. def create_application_emoji(name, image) - image_string = image - if image.respond_to? :read - image_string = 'data:image/jpg;base64,' - image_string += Base64.strict_encode64(image.read) - end + path_method = %i[original_filename path local_path].find { |meth| image.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 image.respond_to? :read + + mime_type = MIME::Types.type_for(image.__send__(path_method)).first&.to_s || 'image/jpeg' + image_string = "data:#{mime_type};base64,#{Base64.encode64(image.read).strip}" response = JSON.parse(API::Application.create_application_emoji(@token, profile.id, name, image_string)) Emoji.new(response, @bot, nil) From 05f65a41831b4163df5bdf02753ba3ff3217962a Mon Sep 17 00:00:00 2001 From: Droid Date: Wed, 30 Oct 2024 23:14:21 -0400 Subject: [PATCH 04/14] Pass in self rather than calling @bot. --- lib/discordrb/bot.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index 23e367326..a89c9535e 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -906,14 +906,14 @@ def edit_application_command_permissions(command_id, server_id, permissions = [] # @return [Array] Returns an array of emoji objects. def application_emojis response = JSON.parse(API::Application.list_application_emojis(@token, profile.id)) - response['items'].map { |emoji| Emoji.new(emoji, @bot, nil) } + response['items'].map { |emoji| Emoji.new(emoji, self, nil) } end # Fetches a single application emoji from ID. # @return [Emoji] Returns an emoji object. def get_application_emoji(emoji_id) response = JSON.parse(API::Application.get_application_emoji(@token, profile.id, emoji_id)) - Emoji.new(response, @bot, nil) + Emoji.new(response, self, nil) end # Adds a new custom emoji that can be used by this application. @@ -930,7 +930,7 @@ def create_application_emoji(name, image) image_string = "data:#{mime_type};base64,#{Base64.encode64(image.read).strip}" response = JSON.parse(API::Application.create_application_emoji(@token, profile.id, name, image_string)) - Emoji.new(response, @bot, nil) + Emoji.new(response, self, nil) end # Edits an existing application emoji. @@ -938,7 +938,7 @@ def create_application_emoji(name, image) # @return [Emoji] Returns the updated emoji object on success. def edit_application_emoji(emoji_id, name) response = JSON.parse(API::Application.edit_application_emoji(@token, profile.id, emoji_id, name)) - Emoji.new(response, @bot, nil) + Emoji.new(response, self, nil) end # Deletes an existing application emoji. From 8c5176a3d0364b1f1b0b5b7f8eab3fa75ee125f1 Mon Sep 17 00:00:00 2001 From: Droid Date: Mon, 16 Dec 2024 11:50:15 -0500 Subject: [PATCH 05/14] Add RL keys. --- lib/discordrb/api/application.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/discordrb/api/application.rb b/lib/discordrb/api/application.rb index 8b91a7d62..c9bb9157d 100644 --- a/lib/discordrb/api/application.rb +++ b/lib/discordrb/api/application.rb @@ -205,7 +205,7 @@ def batch_edit_command_permissions(token, application_id, guild_id, permissions) def list_application_emojis(token, application_id) Discordrb::API.request( :applications_aid_emojis, - nil, + application_id, :get, "#{Discordrb::API.api_base}/applications/#{application_id}/emojis", Authorization: token @@ -217,7 +217,7 @@ def list_application_emojis(token, application_id) def get_application_emoji(token, application_id, emoji_id) Discordrb::API.request( :applications_aid_emojis_eid, - nil, + application_id, :get, "#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}", Authorization: token @@ -229,7 +229,7 @@ def get_application_emoji(token, application_id, emoji_id) def create_application_emoji(token, application_id, name, image) Discordrb::API.request( :applications_aid_emojis, - nil, + application_id, :post, "#{Discordrb::API.api_base}/applications/#{application_id}/emojis", { name: name, image: image }.to_json, @@ -243,7 +243,7 @@ def create_application_emoji(token, application_id, name, image) def edit_application_emoji(token, application_id, emoji_id, name) Discordrb::API.request( :applications_aid_emojis_eid, - nil, + application_id, :patch, "#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}", { name: name }.to_json, @@ -257,7 +257,7 @@ def edit_application_emoji(token, application_id, emoji_id, name) def delete_application_emoji(token, application_id, emoji_id) Discordrb::API.request( :applications_aid_emojis_eid, - nil, + application_id, :delete, "#{Discordrb::API.api_base}/applications/#{application_id}/emojis/#{emoji_id}", Authorization: token From b845893d33c7f4c1d729eaa2c43800390399ca68 Mon Sep 17 00:00:00 2001 From: Droid Date: Mon, 16 Dec 2024 12:01:19 -0500 Subject: [PATCH 06/14] Fix the documentation. --- lib/discordrb/bot.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index a89c9535e..15c067458 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -910,6 +910,7 @@ def application_emojis end # Fetches a single application emoji from ID. + # @param emoji_id [Integer, String] ID of the application emoji to get. # @return [Emoji] Returns an emoji object. def get_application_emoji(emoji_id) response = JSON.parse(API::Application.get_application_emoji(@token, profile.id, emoji_id)) @@ -918,7 +919,7 @@ def get_application_emoji(emoji_id) # Adds a new custom emoji that can be used by this application. # @param name [String] The name of emoji to create. - # @param image [String, #read] An object that responds to `#read`, such as `File`. + # @param image [File, #read] An object that responds to `#read`, such as `File`. # @return [Emoji] The emoji that has been created. def create_application_emoji(name, image) path_method = %i[original_filename path local_path].find { |meth| image.respond_to?(meth) } @@ -934,6 +935,7 @@ def create_application_emoji(name, image) 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) @@ -942,9 +944,10 @@ def edit_application_emoji(emoji_id, name) end # Deletes an existing application emoji. - # @param emoji_id [String] Snowflake ID of the emoji to delete. + # @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) + nil end private From f2785f79fc1b9b68f7e29f061c0a17574e8964b4 Mon Sep 17 00:00:00 2001 From: Droid Date: Tue, 7 Jan 2025 18:22:52 -0500 Subject: [PATCH 07/14] Add new emoji fields, new methods, base64 encoding helper, and remove user account functionality. --- lib/discordrb.rb | 15 ++++++++++ lib/discordrb/api.rb | 51 ------------------------------- lib/discordrb/api/server.rb | 12 ++------ lib/discordrb/api/user.rb | 29 ++++++++---------- lib/discordrb/bot.rb | 30 ++++++++----------- lib/discordrb/data/emoji.rb | 20 +++++++++++++ lib/discordrb/data/profile.rb | 56 ++++++++++------------------------- lib/discordrb/data/server.rb | 22 ++++++-------- lib/discordrb/data/user.rb | 15 ++++++++++ lib/discordrb/gateway.rb | 4 --- 10 files changed, 103 insertions(+), 151 deletions(-) diff --git a/lib/discordrb.rb b/lib/discordrb.rb index fb9474e87..d862a6073 100644 --- a/lib/discordrb.rb +++ b/lib/discordrb.rb @@ -119,6 +119,21 @@ 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, nil] The base64 encoded file, or nil if it couldn't be found. +def encode_file(file) + return unless file != :undef && 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 must respond to read' unless file.respond_to?(:read) + + mime_type = MIME::Types.type_for(file.__send__(path_method)).first&.to_s || 'image/jpeg' + "data:#{mime_type};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 diff --git a/lib/discordrb/api.rb b/lib/discordrb/api.rb index 1e8b0f993..65bf5ace9 100644 --- a/lib/discordrb/api.rb +++ b/lib/discordrb/api.rb @@ -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( @@ -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( @@ -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( diff --git a/lib/discordrb/api/server.rb b/lib/discordrb/api/server.rb index 9b383299e..3d3cc022f 100644 --- a/lib/discordrb/api/server.rb +++ b/lib/discordrb/api/server.rb @@ -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 @@ -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 diff --git a/lib/discordrb/api/user.rb b/lib/discordrb/api/user.rb index 7dc9643ae..a62e049cb 100644 --- a/lib/discordrb/api/user.rb +++ b/lib/discordrb/api/user.rb @@ -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, @@ -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 ) @@ -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) @@ -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 diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index 8bd153a91..c601378f1 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -526,7 +526,7 @@ def parse_mentions(mentions, server = nil) end end elsif /(?^a|^${0}):(?\w+):(?\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 @@ -907,33 +907,27 @@ def edit_application_command_permissions(command_id, server_id, permissions = [] # Fetches all the application emojis that the bot can use. # @return [Array] Returns an array of emoji objects. def application_emojis - response = JSON.parse(API::Application.list_application_emojis(@token, profile.id)) - response['items'].map { |emoji| Emoji.new(emoji, self, nil) } + response = API::Application.list_application_emojis(@token, profile.id) + JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self, nil) } end # Fetches a single application emoji from ID. # @param emoji_id [Integer, String] ID of the application emoji to get. # @return [Emoji] Returns an emoji object. def get_application_emoji(emoji_id) - response = JSON.parse(API::Application.get_application_emoji(@token, profile.id, emoji_id)) - Emoji.new(response, self, nil) + response = API::Application.get_application_emoji(@token, profile.id, emoji_id) + Emoji.new(JSON.parse(response), self, nil) end # Adds a new custom emoji that can be used by this application. # @param name [String] The name of emoji to create. - # @param image [File, #read] An object that responds to `#read`, such as `File`. + # @param image [String, #read] The 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) - path_method = %i[original_filename path local_path].find { |meth| image.respond_to?(meth) } + image = image.respond_to?(:read) ? encode_file(image) : image - raise ArgumentError, 'File object must respond to original_filename, path, or local path.' unless path_method - raise ArgumentError, 'File must respond to read' unless image.respond_to? :read - - mime_type = MIME::Types.type_for(image.__send__(path_method)).first&.to_s || 'image/jpeg' - image_string = "data:#{mime_type};base64,#{Base64.encode64(image.read).strip}" - - response = JSON.parse(API::Application.create_application_emoji(@token, profile.id, name, image_string)) - Emoji.new(response, self, nil) + response = API::Application.create_application_emoji(@token, profile.id, name, image) + Emoji.new(JSON.parse(response), self, nil) end # Edits an existing application emoji. @@ -941,15 +935,14 @@ def create_application_emoji(name, image) # @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 = JSON.parse(API::Application.edit_application_emoji(@token, profile.id, emoji_id, name)) - Emoji.new(response, self, nil) + response = API::Application.edit_application_emoji(@token, profile.id, emoji_id, name) + Emoji.new(JSON.parse(response), self, nil) 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) - nil end private @@ -1762,6 +1755,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 diff --git a/lib/discordrb/data/emoji.rb b/lib/discordrb/data/emoji.rb index 5d81d7183..1a8334efe 100644 --- a/lib/discordrb/data/emoji.rb +++ b/lib/discordrb/data/emoji.rb @@ -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 this emoji is parsed + attr_reader :user + # @return [Array, 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 @@ -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 = User.new(data['user'], bot) if data['user'] process_roles(data['roles']) if server end diff --git a/lib/discordrb/data/profile.rb b/lib/discordrb/data/profile.rb index 91f98c896..8b9ba53e6 100644 --- a/lib/discordrb/data/profile.rb +++ b/lib/discordrb/data/profile.rb @@ -23,49 +23,30 @@ def username=(username) # something readable (e.g. File Object) or as a data URL. def avatar=(avatar) if avatar.respond_to? :read - # Set the file to binary mode if supported, so we don't get problems with Windows - avatar.binmode if avatar.respond_to?(:binmode) - - avatar_string = 'data:image/jpg;base64,' - avatar_string += Base64.strict_encode64(avatar.read) - update_profile_data(avatar: avatar_string) + update_profile_data(avatar: encode_file(avatar)) else update_profile_data(avatar: avatar) end end + # Changes the bot's banner. + # @param banner [String, #read] The base64 encoded string with the image data + # or something that responds to #read. + def banner=(banner) + if banner.respond_to? :read + update_profile_data(banner: encode_file(banner)) + else + update_profile_data(banner: banner) + end + end + # Updates the cached profile data with the new one. # @note For internal use only. # @!visibility private def update_data(new_data) @username = new_data[:username] || @username @avatar_id = new_data[:avatar_id] || @avatar_id - end - - # Sets the user status setting to Online. - # @note Only usable on User accounts. - def online - update_profile_status_setting('online') - end - - # Sets the user status setting to Idle. - # @note Only usable on User accounts. - def idle - update_profile_status_setting('idle') - end - - # Sets the user status setting to Do Not Disturb. - # @note Only usable on User accounts. - def dnd - update_profile_status_setting('dnd') - end - - alias_method(:busy, :dnd) - - # Sets the user status setting to Invisible. - # @note Only usable on User accounts. - def invisible - update_profile_status_setting('invisible') + @banner_id = new_data[:banner_id] || @banner_id end # The inspect method is overwritten to give more useful output @@ -75,16 +56,11 @@ def inspect private - # Internal handler for updating the user's status setting - def update_profile_status_setting(status) - API::User.change_status_setting(@bot.token, status) - end - def update_profile_data(new_data) API::User.update_profile(@bot.token, - nil, nil, - new_data[:username] || @username, - new_data.key?(:avatar) ? new_data[:avatar] : @avatar_id) + new_data.key?(:username) ? new_data[:username] : :undef, + new_data.key?(:avatar) ? new_data[:avatar] : :undef, + new_data.key?(:banner) ? new_data[:banner] : :undef) update_data(new_data) end end diff --git a/lib/discordrb/data/server.rb b/lib/discordrb/data/server.rb index 10a7ce02e..f994f3c70 100644 --- a/lib/discordrb/data/server.rb +++ b/lib/discordrb/data/server.rb @@ -496,7 +496,7 @@ def update_voice_state(data) # @raise [ArgumentError] if type is not 0 (text), 2 (voice), 4 (category), 5 (news), or 6 (store) def create_channel(name, type = 0, topic: nil, bitrate: nil, user_limit: nil, permission_overwrites: nil, parent: nil, nsfw: false, rate_limit_per_user: nil, position: nil, reason: nil) type = Channel::TYPES[type] if type.is_a?(Symbol) - raise ArgumentError, 'Channel type must be either 0 (text), 2 (voice), 4 (category), news (5), or store (6)!' unless [0, 2, 4, 5, 6].include?(type) + raise ArgumentError, 'Channel type must be either 0 (text), 2 (voice), 4 (category), 5 (news), 13 (voice stage), 15 (forum), or 6 (store)!' unless [0, 2, 4, 5, 6, 13, 15].include?(type) permission_overwrites.map! { |e| e.is_a?(Overwrite) ? e.to_hash : e } if permission_overwrites.is_a?(Array) parent_id = parent.respond_to?(:resolve_id) ? parent.resolve_id : nil @@ -512,9 +512,10 @@ def create_channel(name, type = 0, topic: nil, bitrate: nil, user_limit: nil, pe # @param hoist [true, false] # @param mentionable [true, false] # @param permissions [Integer, Array, Permissions, #bits] The permissions to write to the new role. + # @param icon [String, #read] The base64 encoded image data, or a file like object that responds to #read. # @param reason [String] The reason the for the creation of this role. # @return [Role] the created role. - def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, permissions: 104_324_161, reason: nil) + def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, permissions: 104_324_161, icon: nil, reason: nil) colour = colour.respond_to?(:combined) ? colour.combined : colour permissions = if permissions.is_a?(Array) @@ -525,7 +526,9 @@ def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, p permissions end - response = API::Server.create_role(@bot.token, @id, name, colour, hoist, mentionable, permissions, reason) + icon = icon.respond.to?(:read) ? encode_file(icon) : icon + + response = API::Server.create_role(@bot.token, @id, name, colour, hoist, mentionable, permissions, icon, reason) role = Role.new(JSON.parse(response), @bot, self) @roles << role @@ -539,13 +542,8 @@ def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, p # @param reason [String] The reason the for the creation of this emoji. # @return [Emoji] The emoji that has been added. def add_emoji(name, image, roles = [], reason: nil) - image_string = image - if image.respond_to? :read - image_string = 'data:image/jpg;base64,' - image_string += Base64.strict_encode64(image.read) - end - - data = JSON.parse(API::Server.add_emoji(@bot.token, @id, image_string, name, roles.map(&:resolve_id), reason)) + image = image.respond_to?(:read) ? encode_file(image) : image + data = JSON.parse(API::Server.add_emoji(@bot.token, @id, image, name, roles.map(&:resolve_id), reason)) new_emoji = Emoji.new(data, @bot, self) @emoji[new_emoji.id] = new_emoji end @@ -686,9 +684,7 @@ def region=(region) # @param icon [String, #read] The new icon, in base64-encoded JPG format. def icon=(icon) if icon.respond_to? :read - icon_string = 'data:image/jpg;base64,' - icon_string += Base64.strict_encode64(icon.read) - update_server_data(icon_id: icon_string) + update_server_data(icon_id: encode_file(icon)) else update_server_data(icon_id: icon) end diff --git a/lib/discordrb/data/user.rb b/lib/discordrb/data/user.rb index d44949095..51b9672f2 100644 --- a/lib/discordrb/data/user.rb +++ b/lib/discordrb/data/user.rb @@ -49,6 +49,10 @@ module UserAttributes # @see #avatar_url attr_accessor :avatar_id + # @return [String] the ID of this user's current banner, can be used to generate a banner URL. + # @see #banner_url + attr_accessor :banner_id + # Utility function to get Discord's display name of a user not in server # @return [String] the name the user displays as (global_name if they have one, username otherwise) def display_name @@ -86,6 +90,16 @@ def avatar_url(format = nil) API::User.avatar_url(@id, @avatar_id, format) end + # Utility function to get a user's banner URL. + # @param format [String, nil] If `nil`, the URL will default to `png` for static banners and will detect if the user has a `gif` banner. + # You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this. + # @return [String, nil] the URL to the banner image or nil if the user doesn't have one. + def banner_url(format = nil) + return nil unless @banner_id + + API::User.banner_url(@id, @banner_id, format) + end + # @return [Integer] the public flags on a user's account attr_reader :public_flags @@ -120,6 +134,7 @@ def initialize(data, bot) @id = data['id'].to_i @discriminator = data['discriminator'] @avatar_id = data['avatar'] + @banner_id = data['banner'] @roles = {} @activities = Discordrb::ActivitySet.new @public_flags = data['public_flags'] || 0 diff --git a/lib/discordrb/gateway.rb b/lib/discordrb/gateway.rb index 1907a27c3..072d73af1 100644 --- a/lib/discordrb/gateway.rb +++ b/lib/discordrb/gateway.rb @@ -51,10 +51,6 @@ module Opcodes # to behave correctly) VOICE_STATE = 4 - # **Sent**: This opcode is used to ping a voice server, whatever that means. The functionality of this opcode isn't - # known well but non-user clients should never send it. - VOICE_PING = 5 - # **Sent**: This is the other of two possible ways to initiate a gateway session (other than {IDENTIFY}). Rather # than starting an entirely new session, it resumes an existing session by replaying all events from a given # sequence number. It should be used to recover from a connection error or anything like that when the session is From 9b071b83ceca6f58dc8a2e04eb0737422c81878c Mon Sep 17 00:00:00 2001 From: Droid Date: Tue, 7 Jan 2025 19:58:11 -0500 Subject: [PATCH 08/14] Add guard clause for bulk deleting messages. --- lib/discordrb/data/channel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/discordrb/data/channel.rb b/lib/discordrb/data/channel.rb index 4a49894e2..125c8ae11 100644 --- a/lib/discordrb/data/channel.rb +++ b/lib/discordrb/data/channel.rb @@ -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 From 8cecad814115725ebfa4f0b05f673164411050e6 Mon Sep 17 00:00:00 2001 From: Droid Date: Tue, 7 Jan 2025 20:06:43 -0500 Subject: [PATCH 09/14] Remove redundant guard clause. --- lib/discordrb.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/discordrb.rb b/lib/discordrb.rb index d862a6073..4bec2a090 100644 --- a/lib/discordrb.rb +++ b/lib/discordrb.rb @@ -121,17 +121,15 @@ def self.timestamp(time, style = nil) # 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, nil] The base64 encoded file, or nil if it couldn't be found. +# @return [String] The base64 encoded file object as image data. def encode_file(file) - return unless file != :undef && 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 must respond to read' unless file.respond_to?(:read) + raise ArgumentError, 'File object must respond to read.' unless file.respond_to?(:read) - mime_type = MIME::Types.type_for(file.__send__(path_method)).first&.to_s || 'image/jpeg' - "data:#{mime_type};base64,#{Base64.encode64(file.read).strip}" + 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 From 5405543a7c6bef4ce36ed915d51c73cd20852542 Mon Sep 17 00:00:00 2001 From: Droid Date: Tue, 7 Jan 2025 20:16:38 -0500 Subject: [PATCH 10/14] Remove whitespace. --- lib/discordrb/bot.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index c601378f1..223b832a3 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -908,26 +908,25 @@ def edit_application_command_permissions(command_id, server_id, permissions = [] # @return [Array] 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, nil) } + JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) } end - # Fetches a single application emoji from ID. - # @param emoji_id [Integer, String] ID of the application emoji to get. - # @return [Emoji] Returns an emoji object. + # 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, nil) + Emoji.new(JSON.parse(response), self) end - # Adds a new custom emoji that can be used by this application. + # 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] The base64 string with the image data, or an object that responds to #read. + # @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, nil) + Emoji.new(JSON.parse(response), self) end # Edits an existing application emoji. @@ -936,7 +935,7 @@ def create_application_emoji(name, image) # @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, nil) + Emoji.new(JSON.parse(response), self) end # Deletes an existing application emoji. From 837b5e7a3f981885038a73b37159b337a2395db5 Mon Sep 17 00:00:00 2001 From: Droid Date: Thu, 16 Jan 2025 19:59:47 -0500 Subject: [PATCH 11/14] Update Documentation --- lib/discordrb/data/emoji.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/discordrb/data/emoji.rb b/lib/discordrb/data/emoji.rb index 1a8334efe..64d014b75 100644 --- a/lib/discordrb/data/emoji.rb +++ b/lib/discordrb/data/emoji.rb @@ -11,7 +11,7 @@ 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 this emoji is parsed + # @return [User, nil] the user who uploaded this emoji, or nil if the emoji's server is unknown. attr_reader :user # @return [Array, nil] roles this emoji is active for, or nil if the emoji's server is unknown @@ -21,11 +21,11 @@ class Emoji 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 + # @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 + # @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 From 91ae7736f2cb58276f2321213ad440bba4135b75 Mon Sep 17 00:00:00 2001 From: Droid Date: Thu, 16 Jan 2025 20:02:18 -0500 Subject: [PATCH 12/14] Hit the cache --- lib/discordrb/data/emoji.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/discordrb/data/emoji.rb b/lib/discordrb/data/emoji.rb index 64d014b75..785729120 100644 --- a/lib/discordrb/data/emoji.rb +++ b/lib/discordrb/data/emoji.rb @@ -46,7 +46,7 @@ def initialize(data, bot, server = nil) @managed = data['managed'] @available = data['available'] @require_colons = data['require_colons'] - @user = User.new(data['user'], bot) if data['user'] + @user = bot.ensure_user(data['user']) if data['user'] process_roles(data['roles']) if server end From 08058046420934e206114794cc7cc081c2416885 Mon Sep 17 00:00:00 2001 From: Droid Date: Thu, 16 Jan 2025 20:03:45 -0500 Subject: [PATCH 13/14] update the docs --- lib/discordrb/data/emoji.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/discordrb/data/emoji.rb b/lib/discordrb/data/emoji.rb index 785729120..c4d474075 100644 --- a/lib/discordrb/data/emoji.rb +++ b/lib/discordrb/data/emoji.rb @@ -11,7 +11,7 @@ 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. + # @return [User, nil] the user who uploaded this emoji, or nil if the emoji's server is unknown attr_reader :user # @return [Array, nil] roles this emoji is active for, or nil if the emoji's server is unknown From f8b24581093851aea7d8de66aee0b57c3ece04ee Mon Sep 17 00:00:00 2001 From: Droid Date: Thu, 16 Jan 2025 20:15:55 -0500 Subject: [PATCH 14/14] Fix Specs --- lib/discordrb/data/emoji.rb | 2 +- spec/data/emoji_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/discordrb/data/emoji.rb b/lib/discordrb/data/emoji.rb index c4d474075..986ffc4d2 100644 --- a/lib/discordrb/data/emoji.rb +++ b/lib/discordrb/data/emoji.rb @@ -46,7 +46,7 @@ def initialize(data, bot, server = nil) @managed = data['managed'] @available = data['available'] @require_colons = data['require_colons'] - @user = bot.ensure_user(data['user']) if data['user'] + @user = data['user'] ? bot.ensure_user(data['user']) : nil process_roles(data['roles']) if server end diff --git a/spec/data/emoji_spec.rb b/spec/data/emoji_spec.rb index 9803a9a6d..8fa8920fb 100644 --- a/spec/data/emoji_spec.rb +++ b/spec/data/emoji_spec.rb @@ -4,10 +4,12 @@ describe Discordrb::Emoji do let(:bot) { double('bot') } + let(:user) { double('user') } subject(:emoji) do server = double('server', role: double) + expect(bot).to receive(:ensure_user) described_class.new(emoji_data, bot, server) end