From 9ca8ebe76a1d0e761a0b4d990b313da17fb02d49 Mon Sep 17 00:00:00 2001 From: Dakurei Date: Wed, 5 Jul 2023 17:45:56 +0200 Subject: [PATCH] New usernames system (#221) * Add global_name to User after new usernames system + Add User#display_name + Change User#distinct to use discriminator or not, depend to the context (Maybe need change method later, next API versions may remove the discriminator field) + Change Discordrb::API::User#default_avatar & User#avatar_url to use old method to calculate index or not, depend to legacy or not (Maybe need change method later, next API versions may remove the discriminator field) + Change Member#display_name to select global_name before username, but after server nickname + Change Webhook#avatar_url to use new method to calculate index (After testing the webhook creation, the ID already seems to be used for the default avatar instead of the discriminator at 0000) * Modification to allow to determine if User is a Webhook + Without relying on a 0000 discrimator, since it is very likely that this field will disappear completely, sooner or later * Add missing flag + As I saw that the 'ACTIVE_DEVELOPER' flag was missing in the documentation, I added it in the process. * Rubocop friendly * Addition of missing cache edit --- lib/discordrb/api/user.rb | 11 +++++--- lib/discordrb/bot.rb | 9 ++++++- lib/discordrb/data/member.rb | 4 +-- lib/discordrb/data/message.rb | 12 ++++----- lib/discordrb/data/user.rb | 49 ++++++++++++++++++++++++++++------- lib/discordrb/data/webhook.rb | 2 +- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/lib/discordrb/api/user.rb b/lib/discordrb/api/user.rb index e7bd975f0..7dc9643ae 100644 --- a/lib/discordrb/api/user.rb +++ b/lib/discordrb/api/user.rb @@ -132,9 +132,14 @@ def change_status_setting(token, status) ) end - # Returns one of the "default" discord avatars from the CDN given a discriminator - def default_avatar(discrim = 0) - index = discrim.to_i % 5 + # 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) + index = if legacy + discrim_id.to_i % 5 + else + (discrim_id.to_i >> 22) % 5 + end "#{Discordrb::API.cdn_url}/embed/avatars/#{index}.png" end diff --git a/lib/discordrb/bot.rb b/lib/discordrb/bot.rb index a8f78c994..462624a7d 100644 --- a/lib/discordrb/bot.rb +++ b/lib/discordrb/bot.rb @@ -946,10 +946,16 @@ def update_presence(data) username = data['user']['username'] if username && !member_is_new # Don't set the username for newly-cached members - debug "Implicitly updating presence-obtained information for member #{user_id}" + debug "Implicitly updating presence-obtained information username for member #{user_id}" member.update_username(username) end + global_name = data['user']['global_name'] + if global_name && !member_is_new # Don't set the global_name for newly-cached members + debug "Implicitly updating presence-obtained information global_name for member #{user_id}" + member.update_global_name(global_name) + end + member.update_presence(data) member.avatar_id = data['user']['avatar'] if data['user']['avatar'] @@ -1088,6 +1094,7 @@ def update_guild_member(data) member = server.member(data['user']['id'].to_i) member.update_roles(data['roles']) member.update_nick(data['nick']) + member.update_global_name(data['user']['global_name']) if data['user']['global_name'] member.update_boosting_since(data['premium_since']) member.update_communication_disabled_until(data['communication_disabled_until']) end diff --git a/lib/discordrb/data/member.rb b/lib/discordrb/data/member.rb index 3681662a4..0d5a3f8bb 100644 --- a/lib/discordrb/data/member.rb +++ b/lib/discordrb/data/member.rb @@ -289,9 +289,9 @@ def set_nick(nick, reason = nil) alias_method :set_nickname, :set_nick - # @return [String] the name the user displays as (nickname if they have one, username otherwise) + # @return [String] the name the user displays as (nickname if they have one, global_name if they have one, username otherwise) def display_name - nickname || username + nickname || global_name || username end # Update this member's roles diff --git a/lib/discordrb/data/message.rb b/lib/discordrb/data/message.rb index f9e870201..4066f512b 100644 --- a/lib/discordrb/data/message.rb +++ b/lib/discordrb/data/message.rb @@ -73,9 +73,6 @@ class Message # @return [Array] attr_reader :components - # The discriminator that webhook user accounts have. - ZERO_DISCRIM = '0000' - # @!visibility private def initialize(data, bot) @bot = bot @@ -92,12 +89,14 @@ def initialize(data, bot) @server = @channel.server + @webhook_id = data['webhook_id']&.to_i + @author = if data['author'] - if data['author']['discriminator'] == ZERO_DISCRIM + if @webhook_id # This is a webhook user! It would be pointless to try to resolve a member here, so we just create # a User and return that instead. Discordrb::LOGGER.debug("Webhook user: #{data['author']['id']}") - User.new(data['author'], @bot) + User.new(data['author'].merge({ '_webhook' => true }), @bot) elsif @channel.private? # Turn the message user into a recipient - we can't use the channel recipient # directly because the bot may also send messages to the channel @@ -107,6 +106,7 @@ def initialize(data, bot) if member member.update_data(data['member']) if data['member'] + member.update_global_name(data['author']['global_name']) if data['author']['global_name'] else Discordrb::LOGGER.debug("Member with ID #{data['author']['id']} not cached (possibly left the server).") member = if data['member'] @@ -121,8 +121,6 @@ def initialize(data, bot) end end - @webhook_id = data['webhook_id'].to_i if data['webhook_id'] - @timestamp = Time.parse(data['timestamp']) if data['timestamp'] @edited_timestamp = data['edited_timestamp'].nil? ? nil : Time.parse(data['edited_timestamp']) @edited = !@edited_timestamp.nil? diff --git a/lib/discordrb/data/user.rb b/lib/discordrb/data/user.rb index 4b46184b3..b7be8500d 100644 --- a/lib/discordrb/data/user.rb +++ b/lib/discordrb/data/user.rb @@ -18,7 +18,8 @@ module UserAttributes verified_bot: 1 << 16, verified_developer: 1 << 17, certified_moderator: 1 << 18, - bot_http_interactions: 1 << 19 + bot_http_interactions: 1 << 19, + active_developer: 1 << 22 }.freeze # rubocop:enable Naming/VariableNumber @@ -26,6 +27,9 @@ module UserAttributes attr_reader :username alias_method :name, :username + # @return [String, nil] this user's global name + attr_reader :global_name + # @return [String] this user's discriminator which is used internally to identify users with identical usernames. attr_reader :discriminator alias_method :discrim, :discriminator @@ -36,10 +40,21 @@ module UserAttributes attr_reader :bot_account alias_method :bot_account?, :bot_account + # @return [true, false] whether this is fake user for a webhook message + attr_reader :webhook_account + alias_method :webhook_account?, :webhook_account + alias_method :webhook?, :webhook_account + # @return [String] the ID of this user's current avatar, can be used to generate an avatar URL. # @see #avatar_url attr_accessor :avatar_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 + global_name || username + end + # Utility function to mention users in messages # @return [String] the mention code in the form of <@id> def mention @@ -48,15 +63,25 @@ def mention # Utility function to get Discord's distinct representation of a user, i.e. username + discriminator # @return [String] distinct representation of user + # TODO: Maybe change this method again after discriminator removal ? def distinct - "#{@username}##{@discriminator}" + if @discriminator && @discriminator != '0' + "#{@username}##{@discriminator}" + else + @username.to_s + end end # Utility function to get a user's avatar URL. # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the user has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this. Will always be PNG for default avatars. # @return [String] the URL to the avatar image. + # TODO: Maybe change this method again after discriminator removal ? def avatar_url(format = nil) - return API::User.default_avatar(@discriminator) unless @avatar_id + unless @avatar_id + return API::User.default_avatar(@discriminator, legacy: true) if @discriminator && @discriminator != '0' + + return API::User.default_avatar(@id) + end API::User.avatar_url(@id, @avatar_id, format) end @@ -91,6 +116,7 @@ def initialize(data, bot) @bot = bot @username = data['username'] + @global_name = data['global_name'] @id = data['id'].to_i @discriminator = data['discriminator'] @avatar_id = data['avatar'] @@ -101,6 +127,9 @@ def initialize(data, bot) @bot_account = false @bot_account = true if data['bot'] + @webhook_account = false + @webhook_account = true if data['_webhook'] + @status = :offline @client_status = process_client_status(data['client_status']) end @@ -138,13 +167,20 @@ def send_file(file, caption = nil, filename: nil, spoiler: nil) pm.send_file(file, caption: caption, filename: filename, spoiler: spoiler) end - # Set the user's name + # Set the user's username # @note for internal use only # @!visibility private def update_username(username) @username = username end + # Set the user's global_name + # @note For internal use only. + # @!visibility private + def update_global_name(global_name) + @global_name = global_name + end + # Set the user's presence data # @note for internal use only # @!visibility private @@ -183,11 +219,6 @@ def current_bot? @bot.profile.id == @id end - # @return [true, false] whether this user is a fake user for a webhook message - def webhook? - @discriminator == Message::ZERO_DISCRIM - end - # @!visibility private def process_client_status(client_status) (client_status || {}).to_h { |k, v| [k.to_sym, v.to_sym] } diff --git a/lib/discordrb/data/webhook.rb b/lib/discordrb/data/webhook.rb index 218bb897d..3e2bb8260 100644 --- a/lib/discordrb/data/webhook.rb +++ b/lib/discordrb/data/webhook.rb @@ -192,7 +192,7 @@ def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, buil # Utility function to get a webhook's avatar URL. # @return [String] the URL to the avatar image def avatar_url - return API::User.default_avatar unless @avatar + return API::User.default_avatar(@id) unless @avatar API::User.avatar_url(@id, @avatar) end