From 1ef2b112fabf068fc4167e5774b63cd81c9703a4 Mon Sep 17 00:00:00 2001 From: Droid <174426320+Droid00000@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:41:14 -0500 Subject: [PATCH 1/2] feat: Member#modify --- .rubocop.yml | 4 ++ lib/discordrb/api/server.rb | 10 ++- lib/discordrb/data/member.rb | 124 +++++++++++++++++++++------------- lib/discordrb/data/profile.rb | 38 +++++------ 4 files changed, 105 insertions(+), 71 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6fc89dec36..c460d8a519 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -123,3 +123,7 @@ Style/SafeNavigation: Style/Documentation: Enabled: true + +Style/IfUnlessModifier: + Exclude: + - lib/discordrb/data/member.rb \ No newline at end of file diff --git a/lib/discordrb/api/server.rb b/lib/discordrb/api/server.rb index 9a78c64848..ac21969409 100644 --- a/lib/discordrb/api/server.rb +++ b/lib/discordrb/api/server.rb @@ -148,14 +148,20 @@ def update_member(token, server_id, user_id, nick: :undef, roles: :undef, mute: # Update the current member's properties. # https://discord.com/developers/docs/resources/guild#modify-current-member def update_current_member(token, server_id, nick = :undef, reason = nil, bio = :undef, banner = :undef, avatar = :undef) + update_current_member!(token, server_id, nick: nick, avatar: avatar, banner: banner, bio: bio, reason: reason) + end + + # Update the current member's properties. + # https://discord.com/developers/docs/resources/guild#modify-current-member + def update_current_member!(token, server_id, nick: :undef, avatar: :undef, banner: :undef, bio: :undef, reason: nil) Discordrb::API.request( :guilds_sid_members_me, server_id, :patch, "#{Discordrb::API.api_base}/guilds/#{server_id}/members/@me", - { nick: nick, bio: bio, banner: banner, avatar: avatar }.reject { |_, v| v == :undef }.to_json, - Authorization: token, + { nick:, avatar:, banner:, bio: }.reject { |_, value| value == :undef }.to_json, content_type: :json, + Authorization: token, 'X-Audit-Log-Reason': reason ) end diff --git a/lib/discordrb/data/member.rb b/lib/discordrb/data/member.rb index 855afa4f6d..236898f659 100644 --- a/lib/discordrb/data/member.rb +++ b/lib/discordrb/data/member.rb @@ -191,9 +191,7 @@ def communication_disabled? # Set a user's timeout duration, or remove it by setting the timeout to `nil`. # @param timeout_until [Time, nil] When the timeout will end. def communication_disabled_until=(timeout_until) - raise ArgumentError, 'A time out cannot exceed 28 days' if timeout_until && timeout_until > (Time.now + 2_419_200) - - update_member_data(communication_disabled_until: timeout_until&.iso8601) + modify(communication_disabled_until: timeout_until) end alias_method :timeout=, :communication_disabled_until= @@ -202,8 +200,7 @@ def communication_disabled_until=(timeout_until) # @param role [Role, Array] The role(s) to set. # @param reason [String] The reason the user's roles are being changed. def set_roles(role, reason = nil) - role_ids = role_id_array(role) - update_member_data(roles: role_ids, reason: reason) + modify(roles: role_id_array(role), reason: reason) end # Adds and removes roles from a member. @@ -220,7 +217,7 @@ def modify_roles(add, remove, reason = nil) old_role_ids = resolve_role_ids new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq - update_member_data(roles: new_role_ids, reason: reason) + modify(roles: new_role_ids, reason: reason) end # Adds one or more roles to this member. @@ -234,7 +231,7 @@ def add_role(role, reason = nil) else old_role_ids = resolve_role_ids new_role_ids = (old_role_ids + role_ids).uniq - update_member_data(roles: new_role_ids, reason: reason) + modify(roles: new_role_ids, reason: reason) end end @@ -249,7 +246,7 @@ def remove_role(role, reason = nil) else old_role_ids = resolve_role_ids new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) } - update_member_data(roles: new_role_ids, reason: reason) + modify(roles: new_role_ids, reason: reason) end end @@ -294,25 +291,25 @@ def sort_roles # Server deafens this member. # @param reason [String, nil] The reason for defeaning this member. def server_deafen(reason: nil) - update_member_data(deaf: true, reason: reason) + modify(deaf: true, reason: reason) end # Server undeafens this member. # @param reason [String, nil] The reason for un-defeaning this member. def server_undeafen(reason: nil) - update_member_data(deaf: false, reason: reason) + modify(deaf: false, reason: reason) end # Server mutes this member. # @param reason [String, nil] The reason for muting this member. def server_mute(reason: nil) - update_member_data(mute: true, reason: reason) + modify(mute: true, reason: reason) end # Server unmutes this member. # @param reason [String, nil] The reason for un-muting this member. def server_unmute(reason: nil) - update_member_data(mute: false, reason: reason) + modify(mute: false, reason: reason) end # Bans this member from the server. @@ -347,11 +344,7 @@ def nick=(nick) # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset. # @param reason [String] The reason the user's nickname is being changed. def set_nick(nick, reason = nil) - if @user.current_bot? - update_current_member_data(nick: nick, reason: reason) - else - update_member_data(nick: nick, reason: reason) - end + modify(nick: nick, reason: reason) end alias_method :set_nickname, :set_nick @@ -381,7 +374,7 @@ def display_avatar_decoration # Set the flags for this member. # @param flags [Integer, nil] The new bitwise value of flags for this member, or nil. def flags=(flags) - update_member_data(flags: flags) + modify(flags: flags) end # Set the server banner for the current bot. @@ -389,7 +382,7 @@ def flags=(flags) def server_banner=(banner) raise 'Can only set a banner for the current bot' unless current_bot? - update_current_member_data(banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner) + modify(banner: banner) end # Set the server avatar for the current bot. @@ -397,7 +390,7 @@ def server_banner=(banner) def server_avatar=(avatar) raise 'Can only set an avatar for the current bot' unless current_bot? - update_current_member_data(avatar: avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar) + modify(avatar: avatar) end # Set the server bio for the current bot. @@ -405,7 +398,62 @@ def server_avatar=(avatar) def server_bio=(bio) raise 'Can only set a bio for the current bot' unless current_bot? - update_current_member_data(bio: bio) + modify(bio: bio) + end + + # Modify the properties of the member. + # @param nick [String, nil] The new nickname of the member. + # @param roles [Array, nil] The new roles to set for the member. + # @param mute [true, false, nil] Whether the member shoule be muted in the voice channel. + # @param deaf [true, false, nil] Whether the member should be deafened in the voice channel. + # @param channel [Channel, Integer, String, nil] The voice channel to move the member to. + # @param communication_disabled_until [Time, nil] When the member's timeout should expire. + # Must be a value between now and 28 days in the future. + # @param flags [Integer, nil] The new flags to set for the member. + # @param avatar [#read, File, nil] The new server avatar to set for the current bot. Should be + # a file-like object that responds to `#read`. + # @param banner [#read, File, nil] The new server banner to set for the current bot. Should be + # a file-like object that responds to `#read`. + # @param bio [String, nil] The new server bio to set for the current bot. + # @param reason [String, nil] The reason to show in the audit log for modifying the member. + # @return [nil] + def modify( + nick: :undef, roles: :undef, mute: :undef, deaf: :undef, channel: :undef, timeout_until: :undef, + communication_disabled_until: :undef, flags: :undef, avatar: :undef, banner: :undef, bio: :undef, + reason: nil + ) + timeout = communication_disabled_until == :undef ? timeout_until : communication_disabled_until + + if timeout.respond_to?(:iso8601) && timeout > (Time.now + 2_419_200) + raise ArgumentError, 'The timeout duration cannot be greater than 28 days in the future' + end + + data = { + nick: nick, + roles: roles == :undef ? roles : roles&.map(&:resolve_id), + mute: mute, + deaf: deaf, + channel_id: channel == :undef ? channel : channel&.resolve_id, + flags: flags, + communication_disabled_until: timeout == :undef ? timeout : timeout&.iso8601 + } + + if current_bot? && (nick != :undef || avatar != :undef || banner != :undef || bio != :undef) + bot_data = { + bio: bio, + nick: nick, + avatar: avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar, + banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner, + reason: reason + } + + data[:nick] = :undef + update_data(JSON.parse(API::Server.update_current_member!(@bot.token, @server_id, **bot_data))) + return unless data.any? { |_, value| value != :undef } + end + + update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **data, reason:))) + nil end # Update this member's roles @@ -485,39 +533,19 @@ def inspect private - # Utility method to get a list of role IDs from one role or an array of roles - def role_id_array(role) - if role.is_a? Array - role.map(&:resolve_id) - else - [role.resolve_id] - end - end - - # Utility method to get data out of this member's voice state - def voice_state_attribute(name) - voice_state = server.voice_states[@user.id] - voice_state&.send name - end - - # @!visibility private + # Utility method to get a list of this member's role IDs. def resolve_role_ids @roles ? @roles.collect(&:id) : @role_ids end - # @!visibility private - def update_member_data(new_data) - update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **new_data))) + # Utility method to get data out of this member's voice state. + def voice_state_attribute(name) + server.voice_states[@user.resolve_id]&.send(name) end - # @!visibility private - def update_current_member_data(new_data) - update_data(JSON.parse(API::Server.update_current_member(@bot.token, @server_id, - new_data.key?(:nick) ? new_data[:nick] : :undef, - new_data[:reason], - new_data.key?(:bio) ? new_data[:bio] : :undef, - new_data.key?(:banner) ? new_data[:banner] : :undef, - new_data.key?(:avatar) ? new_data[:avatar] : :undef))) + # Utility method to get a list of role IDs from one role or an array of roles. + def role_id_array(role) + role.is_a?(Array) ? role.map(&:resolve_id) : [role.resolve_id] end end end diff --git a/lib/discordrb/data/profile.rb b/lib/discordrb/data/profile.rb index edc8254901..8b1bfb9434 100644 --- a/lib/discordrb/data/profile.rb +++ b/lib/discordrb/data/profile.rb @@ -13,7 +13,7 @@ def current_bot? # Sets the bot's username. # @param username [String] The new username. def username=(username) - update_profile_data(username: username) + modify(username: username) end alias_method :name=, :username= @@ -22,22 +22,28 @@ def username=(username) # @param avatar [String, File, #read, nil] A file to be used as the avatar, either # something readable (e.g. File Object) or a data URI. def avatar=(avatar) - if avatar.respond_to?(:read) - update_profile_data(avatar: Discordrb.encode64(avatar)) - else - update_profile_data(avatar: avatar) - end + modify(avatar: avatar) end # Changes the bot's banner. # @param banner [String, File, #read, nil] A file to be used as the banner, either # something readable (e.g. File Object) or a data URI. def banner=(banner) - if banner.respond_to?(:read) - update_profile_data(banner: Discordrb.encode64(banner)) - else - update_profile_data(banner: banner) - end + modify(banner: banner) + end + + # Modify the properties of the current bot. + # @param username [String] The new username to set for the bot. + # @param avatar [String, File, #read, nil] The new avatar to set for the bot. Should + # be something readable (e.g. File Object) or a data URI. + # @param banner [String, File, #read, nil] The new banner to set for the bot. Should + # be something readable (e.g. File Object) or a data URI. + # @return [nil] + def modify(username: :undef, avatar: :undef, banner: :undef) + avatar = avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar + banner = banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner + update_data(JSON.parse(API::User.update_current_user(@bot.token, username, avatar, banner))) + nil end # Updates the cached profile data with the new one. @@ -53,15 +59,5 @@ def update_data(new_data) def inspect "" end - - private - - # @!visibility private - def update_profile_data(new_data) - update_data(JSON.parse(API::User.update_current_user(@bot.token, - new_data[:username] || :undef, - new_data.key?(:avatar) ? new_data[:avatar] : :undef, - new_data.key?(:banner) ? new_data[:banner] : :undef))) - end end end From 8d1c91e666245ede8181d2300a6b5d368227c3e3 Mon Sep 17 00:00:00 2001 From: Droid <174426320+Droid00000@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:33:15 -0400 Subject: [PATCH 2/2] feat: rename communication_disabled_until to timeout_until --- lib/discordrb/api/user.rb | 18 ++++++++---- lib/discordrb/data/member.rb | 52 ++++++++++++++++------------------- lib/discordrb/data/profile.rb | 22 +++++++++++---- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/lib/discordrb/api/user.rb b/lib/discordrb/api/user.rb index 706789e172..e78b3a1772 100644 --- a/lib/discordrb/api/user.rb +++ b/lib/discordrb/api/user.rb @@ -43,23 +43,29 @@ def change_own_nickname(token, server_id, nick, reason = nil) ) end - # @deprecated Please use {update_current_user} instead. + # @deprecated Please use {update_current_user!} instead. # https://discord.com/developers/docs/resources/user#modify-current-user def update_profile(token, _email, _password, new_username, avatar, _new_password = nil) - update_current_user(token, new_username, avatar) + update_current_user!(token, username: new_username, avatar: avatar) end - # Update the properties of the user for the current bot. + # @deprecated Please use {#update_current_user!} instead. # https://discord.com/developers/docs/resources/user#modify-current-user def update_current_user(token, username = :undef, avatar = :undef, banner = :undef) + update_current_user!(token, username: username, avatar: avatar, banner: banner) + end + + # Update the properties of the user for the current bot. + # https://discord.com/developers/docs/resources/user#modify-current-user + def update_current_user!(token, username: :undef, avatar: :undef, banner: :undef) Discordrb::API.request( :users_me, nil, :patch, "#{Discordrb::API.api_base}/users/@me", - { username: username, avatar: avatar, banner: banner }.reject { |_, value| value == :undef }.to_json, - Authorization: token, - content_type: :json + { username:, avatar:, banner: }.reject { |_, value| value == :undef }.to_json, + content_type: :json, + Authorization: token ) end diff --git a/lib/discordrb/data/member.rb b/lib/discordrb/data/member.rb index 236898f659..2bad1cca57 100644 --- a/lib/discordrb/data/member.rb +++ b/lib/discordrb/data/member.rb @@ -33,9 +33,10 @@ module MemberAttributes # @return [Server] the server this member is on. attr_reader :server - # @return [Time] When the user's timeout will expire. + # @return [Time, nil] When the user's timeout will expire. attr_reader :communication_disabled_until alias_method :timeout, :communication_disabled_until + alias_method :timeout_until, :communication_disabled_until # @return [Integer] the flags set on this member. attr_reader :flags @@ -191,7 +192,7 @@ def communication_disabled? # Set a user's timeout duration, or remove it by setting the timeout to `nil`. # @param timeout_until [Time, nil] When the timeout will end. def communication_disabled_until=(timeout_until) - modify(communication_disabled_until: timeout_until) + modify(timeout_until: timeout_until) end alias_method :timeout=, :communication_disabled_until= @@ -402,57 +403,50 @@ def server_bio=(bio) end # Modify the properties of the member. - # @param nick [String, nil] The new nickname of the member. + # @param nick [String, nil] The new nickname of the member; between 1-32 characters. Can also be passed as `nickname:`. # @param roles [Array, nil] The new roles to set for the member. # @param mute [true, false, nil] Whether the member shoule be muted in the voice channel. # @param deaf [true, false, nil] Whether the member should be deafened in the voice channel. - # @param channel [Channel, Integer, String, nil] The voice channel to move the member to. - # @param communication_disabled_until [Time, nil] When the member's timeout should expire. - # Must be a value between now and 28 days in the future. - # @param flags [Integer, nil] The new flags to set for the member. - # @param avatar [#read, File, nil] The new server avatar to set for the current bot. Should be - # a file-like object that responds to `#read`. - # @param banner [#read, File, nil] The new server banner to set for the current bot. Should be - # a file-like object that responds to `#read`. - # @param bio [String, nil] The new server bio to set for the current bot. - # @param reason [String, nil] The reason to show in the audit log for modifying the member. + # @param voice_channel [Channel, Integer, String, nil] The voice channel to move the member to. + # @param timeout_until [Time, nil] When the member's timeout should expire. Must be a value between now and 28 days in the future. + # @param flags [Integer, nil] The new flags to set for the member. The only flag that can currently be changed is the `bypassed_verification` flag. + # @param avatar [#read, File, nil] The new server-specific avatar to set for the current bot. Should be a file-like object that responds to `#read`. + # @param banner [#read, File, nil] The new server-specific banner to set for the current bot. Should be a file-like object that responds to `#read`. + # @param bio [String, nil] The new server-specific bio to set for the current bot; between 1-190 characters. + # @param reason [String, nil] The reason to show in the server's audit log for modifying the member. # @return [nil] def modify( - nick: :undef, roles: :undef, mute: :undef, deaf: :undef, channel: :undef, timeout_until: :undef, - communication_disabled_until: :undef, flags: :undef, avatar: :undef, banner: :undef, bio: :undef, - reason: nil + nick: :undef, roles: :undef, mute: :undef, deaf: :undef, voice_channel: :undef, + timeout_until: :undef, flags: :undef, avatar: :undef, banner: :undef, bio: :undef, + nickname: :undef, reason: nil ) - timeout = communication_disabled_until == :undef ? timeout_until : communication_disabled_until - - if timeout.respond_to?(:iso8601) && timeout > (Time.now + 2_419_200) + if timeout_until.is_a?(Time) && timeout_until > (Time.now + 2_419_200) raise ArgumentError, 'The timeout duration cannot be greater than 28 days in the future' end data = { - nick: nick, + nick: nick == :undef ? nickname : nick, roles: roles == :undef ? roles : roles&.map(&:resolve_id), mute: mute, deaf: deaf, - channel_id: channel == :undef ? channel : channel&.resolve_id, + channel_id: voice_channel == :undef ? voice_channel : voice_channel&.resolve_id, flags: flags, - communication_disabled_until: timeout == :undef ? timeout : timeout&.iso8601 + communication_disabled_until: timeout_until == :undef ? timeout_until : timeout_until&.iso8601 } if current_bot? && (nick != :undef || avatar != :undef || banner != :undef || bio != :undef) - bot_data = { + me = { bio: bio, - nick: nick, + nick: data.delete(:nick), avatar: avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar, - banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner, - reason: reason + banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner } - data[:nick] = :undef - update_data(JSON.parse(API::Server.update_current_member!(@bot.token, @server_id, **bot_data))) + update_data(JSON.parse(API::Server.update_current_member!(@bot.token, @server_id, **me, reason: reason))) return unless data.any? { |_, value| value != :undef } end - update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **data, reason:))) + update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **data, reason: reason))) nil end diff --git a/lib/discordrb/data/profile.rb b/lib/discordrb/data/profile.rb index f35e3a00eb..71a8a282cf 100644 --- a/lib/discordrb/data/profile.rb +++ b/lib/discordrb/data/profile.rb @@ -38,11 +38,23 @@ def banner=(banner) # be something readable (e.g. File Object) or a data URI. # @param banner [String, File, #read, nil] The new banner to set for the bot. Should # be something readable (e.g. File Object) or a data URI. + # @param bio [String, nil] The new global bio to set for the bot. Will be truncated to + # 190 characters when rendered in the offical Discord client. # @return [nil] - def modify(username: :undef, avatar: :undef, banner: :undef) - avatar = avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar - banner = banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner - update_data(JSON.parse(API::User.update_current_user(@bot.token, username, avatar, banner))) + def modify(username: :undef, avatar: :undef, banner: :undef, bio: :undef) + data = { + username: username, + avatar: avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar, + banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner + } + + if bio != :undef + API::Application.update_current_application(@bot.token, description: bio) + + return unless data.any? { |_, value| value != :undef } + end + + update_data(JSON.parse(API::User.update_current_user!(@bot.token, **data))) nil end @@ -55,7 +67,7 @@ def bio # Set the bot's global bio. # @param bio [String, nil] The bot's new global bio, or `nil` to remove the current bio. def bio=(bio) - @bot.application.modify(description: bio) + modify(bio: bio) end # Updates the cached profile data with the new one.