You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

541 lines
13 KiB
Lua

--[=[
@c Member x UserPresence
@d Represents a Discord guild member. Though one user may be a member in more than
one guild, each presence is represented by a different member object associated
with that guild. Note that any method or property that exists for the User class is
also available in the Member class.
]=]
local json = require('json')
local enums = require('enums')
local class = require('class')
local UserPresence = require('containers/abstract/UserPresence')
local ArrayIterable = require('iterables/ArrayIterable')
local Color = require('utils/Color')
local Resolver = require('client/Resolver')
local GuildChannel = require('containers/abstract/GuildChannel')
local Permissions = require('utils/Permissions')
local insert, remove, sort = table.insert, table.remove, table.sort
local band, bor, bnot = bit.band, bit.bor, bit.bnot
local isInstance = class.isInstance
local permission = enums.permission
local Member, get = class('Member', UserPresence)
function Member:__init(data, parent)
UserPresence.__init(self, data, parent)
return self:_loadMore(data)
end
function Member:_load(data)
UserPresence._load(self, data)
return self:_loadMore(data)
end
function Member:_loadMore(data)
if data.roles then
local roles = #data.roles > 0 and data.roles or nil
if self._roles then
self._roles._array = roles
else
self._roles_raw = roles
end
end
end
local function sorter(a, b)
if a._position == b._position then
return tonumber(a._id) < tonumber(b._id)
else
return a._position > b._position
end
end
local function predicate(role)
return role._color > 0
end
--[=[
@m getColor
@t mem
@r Color
@d Returns a color object that represents the member's color as determined by
its highest colored role. If the member has no colored roles, then the default
color with a value of 0 is returned.
]=]
function Member:getColor()
local roles = {}
for role in self.roles:findAll(predicate) do
insert(roles, role)
end
sort(roles, sorter)
return roles[1] and roles[1]:getColor() or Color()
end
local function has(a, b)
return band(a, b) > 0
end
--[=[
@m hasPermission
@t mem
@op channel GuildChannel
@p perm Permissions-Resolvable
@r boolean
@d Checks whether the member has a specific permission. If `channel` is omitted,
then only guild-level permissions are checked. This is a relatively expensive
operation. If you need to check multiple permissions at once, use the
`getPermissions` method and check the resulting object.
]=]
function Member:hasPermission(channel, perm)
if not perm then
perm = channel
channel = nil
end
local guild = self.guild
if channel then
if not isInstance(channel, GuildChannel) or channel.guild ~= guild then
return error('Invalid GuildChannel: ' .. tostring(channel), 2)
end
end
local n = Resolver.permission(perm)
if not n then
return error('Invalid permission: ' .. tostring(perm), 2)
end
if self.id == guild.ownerId then
return true
end
local rolePermissions = guild.defaultRole.permissions
for role in self.roles:iter() do
if role.id ~= guild.id then -- just in case
rolePermissions = bor(rolePermissions, role.permissions)
end
end
if has(rolePermissions, permission.administrator) then
return true
end
if channel then
local overwrites = channel.permissionOverwrites
local overwrite = overwrites:get(self.id)
if overwrite then
if has(overwrite.allowedPermissions, n) then
return true
end
if has(overwrite.deniedPermissions, n) then
return false
end
end
local allow, deny = 0, 0
for role in self.roles:iter() do
if role.id ~= guild.id then -- just in case
overwrite = overwrites:get(role.id)
if overwrite then
allow = bor(allow, overwrite.allowedPermissions)
deny = bor(deny, overwrite.deniedPermissions)
end
end
end
if has(allow, n) then
return true
end
if has(deny, n) then
return false
end
local everyone = overwrites:get(guild.id)
if everyone then
if has(everyone.allowedPermissions, n) then
return true
end
if has(everyone.deniedPermissions, n) then
return false
end
end
end
return has(rolePermissions, n)
end
--[=[
@m getPermissions
@t mem
@op channel GuildChannel
@r Permissions
@d Returns a permissions object that represents the member's total permissions for
the guild, or for a specific channel if one is provided. If you just need to
check one permission, use the `hasPermission` method.
]=]
function Member:getPermissions(channel)
local guild = self.guild
if channel then
if not isInstance(channel, GuildChannel) or channel.guild ~= guild then
return error('Invalid GuildChannel: ' .. tostring(channel), 2)
end
end
if self.id == guild.ownerId then
return Permissions.all()
end
local ret = guild.defaultRole.permissions
for role in self.roles:iter() do
if role.id ~= guild.id then -- just in case
ret = bor(ret, role.permissions)
end
end
if band(ret, permission.administrator) > 0 then
return Permissions.all()
end
if channel then
local overwrites = channel.permissionOverwrites
local everyone = overwrites:get(guild.id)
if everyone then
ret = band(ret, bnot(everyone.deniedPermissions))
ret = bor(ret, everyone.allowedPermissions)
end
local allow, deny = 0, 0
for role in self.roles:iter() do
if role.id ~= guild.id then -- just in case
local overwrite = overwrites:get(role.id)
if overwrite then
deny = bor(deny, overwrite.deniedPermissions)
allow = bor(allow, overwrite.allowedPermissions)
end
end
end
ret = band(ret, bnot(deny))
ret = bor(ret, allow)
local overwrite = overwrites:get(self.id)
if overwrite then
ret = band(ret, bnot(overwrite.deniedPermissions))
ret = bor(ret, overwrite.allowedPermissions)
end
end
return Permissions(ret)
end
--[=[
@m addRole
@t http?
@p id Role-ID-Resolvable
@r boolean
@d Adds a role to the member. If the member already has the role, then no action is
taken. Note that the everyone role cannot be explicitly added.
]=]
function Member:addRole(id)
if self:hasRole(id) then return true end
id = Resolver.roleId(id)
local data, err = self.client._api:addGuildMemberRole(self._parent._id, self.id, id)
if data then
local roles = self._roles and self._roles._array or self._roles_raw
if roles then
insert(roles, id)
else
self._roles_raw = {id}
end
return true
else
return false, err
end
end
--[=[
@m removeRole
@t http?
@p id Role-ID-Resolvable
@r boolean
@d Removes a role from the member. If the member does not have the role, then no
action is taken. Note that the everyone role cannot be removed.
]=]
function Member:removeRole(id)
if not self:hasRole(id) then return true end
id = Resolver.roleId(id)
local data, err = self.client._api:removeGuildMemberRole(self._parent._id, self.id, id)
if data then
local roles = self._roles and self._roles._array or self._roles_raw
if roles then
for i, v in ipairs(roles) do
if v == id then
remove(roles, i)
break
end
end
if #roles == 0 then
if self._roles then
self._roles._array = nil
else
self._roles_raw = nil
end
end
end
return true
else
return false, err
end
end
--[=[
@m hasRole
@t mem
@p id Role-ID-Resolvable
@r boolean
@d Checks whether the member has a specific role. This will return true for the
guild's default role in addition to any explicitly assigned roles.
]=]
function Member:hasRole(id)
id = Resolver.roleId(id)
if id == self._parent._id then return true end -- @everyone
local roles = self._roles and self._roles._array or self._roles_raw
if roles then
for _, v in ipairs(roles) do
if v == id then
return true
end
end
end
return false
end
--[=[
@m setNickname
@t http
@p nick string
@r boolean
@d Sets the member's nickname. This must be between 1 and 32 characters in length.
Pass `nil` to remove the nickname.
]=]
function Member:setNickname(nick)
nick = nick or ''
local data, err
if self.id == self.client._user._id then
data, err = self.client._api:modifyCurrentUsersNick(self._parent._id, {nick = nick})
else
data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {nick = nick})
end
if data then
self._nick = nick ~= '' and nick or nil
return true
else
return false, err
end
end
--[=[
@m setVoiceChannel
@t http
@p id Channel-ID-Resolvable
@r boolean
@d Moves the member to a new voice channel, but only if the member has an active
voice connection in the current guild. Due to complexities in voice state
handling, the member's `voiceChannel` property will update asynchronously via
WebSocket; not as a result of the HTTP request.
]=]
function Member:setVoiceChannel(id)
id = id and Resolver.channelId(id)
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {channel_id = id or json.null})
if data then
return true
else
return false, err
end
end
--[=[
@m mute
@t http
@r boolean
@d Mutes the member in its guild.
]=]
function Member:mute()
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {mute = true})
if data then
self._mute = true
return true
else
return false, err
end
end
--[=[
@m unmute
@t http
@r boolean
@d Unmutes the member in its guild.
]=]
function Member:unmute()
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {mute = false})
if data then
self._mute = false
return true
else
return false, err
end
end
--[=[
@m deafen
@t http
@r boolean
@d Deafens the member in its guild.
]=]
function Member:deafen()
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {deaf = true})
if data then
self._deaf = true
return true
else
return false, err
end
end
--[=[
@m undeafen
@t http
@r boolean
@d Undeafens the member in its guild.
]=]
function Member:undeafen()
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {deaf = false})
if data then
self._deaf = false
return true
else
return false, err
end
end
--[=[
@m kick
@t http
@p reason string
@r boolean
@d Equivalent to `Member.guild:kickUser(Member.user, reason)`
]=]
function Member:kick(reason)
return self._parent:kickUser(self._user, reason)
end
--[=[
@m ban
@t http
@p reason string
@p days number
@r boolean
@d Equivalent to `Member.guild:banUser(Member.user, reason, days)`
]=]
function Member:ban(reason, days)
return self._parent:banUser(self._user, reason, days)
end
--[=[
@m unban
@t http
@p reason string
@r boolean
@d Equivalent to `Member.guild:unbanUser(Member.user, reason)`
]=]
function Member:unban(reason)
return self._parent:unbanUser(self._user, reason)
end
--[=[@p roles ArrayIterable An iterable array of guild roles that the member has. This does not explicitly
include the default everyone role. Object order is not guaranteed.]=]
function get.roles(self)
if not self._roles then
local roles = self._parent._roles
self._roles = ArrayIterable(self._roles_raw, function(id)
return roles:get(id)
end)
self._roles_raw = nil
end
return self._roles
end
--[=[@p name string If the member has a nickname, then this will be equivalent to that nickname.
Otherwise, this is equivalent to `Member.user.username`.]=]
function get.name(self)
return self._nick or self._user._username
end
--[=[@p nickname string/nil The member's nickname, if one is set.]=]
function get.nickname(self)
return self._nick
end
--[=[@p joinedAt string/nil The date and time at which the current member joined the guild, represented as
an ISO 8601 string plus microseconds when available. Member objects generated
via presence updates lack this property.]=]
function get.joinedAt(self)
return self._joined_at
end
--[=[@p premiumSince string/nil The date and time at which the current member boosted the guild, represented as
an ISO 8601 string plus microseconds when available.]=]
function get.premiumSince(self)
return self._premium_since
end
--[=[@p voiceChannel GuildVoiceChannel/nil The voice channel to which this member is connected in the current guild.]=]
function get.voiceChannel(self)
local guild = self._parent
local state = guild._voice_states[self:__hash()]
return state and guild._voice_channels:get(state.channel_id)
end
--[=[@p muted boolean Whether the member is voice muted in its guild.]=]
function get.muted(self)
local state = self._parent._voice_states[self:__hash()]
return state and (state.mute or state.self_mute) or self._mute
end
--[=[@p deafened boolean Whether the member is voice deafened in its guild.]=]
function get.deafened(self)
local state = self._parent._voice_states[self:__hash()]
return state and (state.deaf or state.self_deaf) or self._deaf
end
--[=[@p guild Guild The guild in which this member exists.]=]
function get.guild(self)
return self._parent
end
--[=[@p highestRole Role The highest positioned role that the member has. If the member has no
explicit roles, then this is equivalent to `Member.guild.defaultRole`.]=]
function get.highestRole(self)
local ret
for role in self.roles:iter() do
if not ret or sorter(role, ret) then
ret = role
end
end
return ret or self.guild.defaultRole
end
return Member