diff --git a/.rubocop.yml b/.rubocop.yml index 100d21b06d..1f6638f20c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -126,4 +126,5 @@ Style/Documentation: Style/IfUnlessModifier: Exclude: - - lib/discordrb/data/role.rb \ No newline at end of file + - lib/discordrb/data/member.rb + - lib/discordrb/data/role.rb diff --git a/lib/discordrb/api/server.rb b/lib/discordrb/api/server.rb index 0b44657a75..18d8180c47 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/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 855afa4f6d..cf88663750 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 @@ -110,6 +111,7 @@ def voice_channel alias_method :self_deafened?, :self_deaf include MemberAttributes + include PermissionCalculator # @!visibility private def initialize(data, server, bot) @@ -191,9 +193,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(timeout_until: timeout_until) end alias_method :timeout=, :communication_disabled_until= @@ -202,8 +202,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 +219,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 +233,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 +248,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 +293,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 +346,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 +376,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 +384,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 +392,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,44 +400,57 @@ 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) - end - - # Update this member's roles - # @note For internal use only. - # @!visibility private - def update_roles(role_ids) - @roles = [server.role(@server_id)] - role_ids.each do |id| - # It is possible for members to have roles that do not exist - # on the server any longer. See https://github.com/discordrb/discordrb/issues/371 - role = server.role(id) - @roles << role if role + modify(bio: bio) + end + + # Modify the properties 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 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, voice_channel: :undef, + timeout_until: :undef, flags: :undef, avatar: :undef, banner: :undef, bio: :undef, + nickname: :undef, reason: nil + ) + 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 - end - - # Update this member's nick - # @note For internal use only. - # @!visibility private - def update_nick(nick) - @nick = nick - end - # Update this member's boosting timestamp - # @note For internal user only. - # @!visibility private - def update_boosting_since(time) - @boosting_since = time - end + data = { + nick: nick == :undef ? nickname : nick, + roles: roles == :undef ? roles : roles&.map(&:resolve_id), + mute: mute, + deaf: deaf, + channel_id: voice_channel == :undef ? voice_channel : voice_channel&.resolve_id, + flags: flags, + communication_disabled_until: timeout_until == :undef ? timeout_until : timeout_until&.iso8601 + } + + if current_bot? && (nick != :undef || avatar != :undef || banner != :undef || bio != :undef) + me = { + bio: bio, + nick: data.delete(:nick), + avatar: avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar, + banner: banner.respond_to?(:read) ? Discordrb.encode64(banner) : banner + } + + update_data(JSON.parse(API::Server.update_current_member!(@bot.token, @server_id, **me, reason: reason))) + return unless data.any? { |_, value| value != :undef } + end - # @!visibility private - def update_communication_disabled_until(time) - time = time ? Time.parse(time) : nil - @communication_disabled_until = time + update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **data, reason: reason))) + nil end - # Update this member - # @note For internal use only. # @!visibility private def update_data(data) update_roles(data['roles']) if data['roles'] @@ -476,48 +484,38 @@ def update_data(data) @server_avatar_decoration = process_avatar_decoration(data['avatar_decoration_data']) if data.key?('avatar_decoration_data') end - include PermissionCalculator - - # Overwriting inspect for debug purposes - def inspect - "" - end + # @!visibility private + def update_roles(role_ids) + items = role_ids.filter_map { |id| server.role(id) } - private + (items << server.everyone_role) if items.none?(@server_id) - # 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 + @roles = items 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 + # @!visibility private + def inspect + "" end + private + # @!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.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 8c544dcff0..71a8a282cf 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,40 @@ 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) + 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. + # @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, 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 # Get the bot's global bio. @@ -49,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. @@ -65,15 +83,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