package api import ( "github.com/diamondburned/arikawa/discord" "github.com/diamondburned/arikawa/utils/httputil" "github.com/diamondburned/arikawa/utils/json/option" ) const maxMemberFetchLimit = 1000 // Member returns a guild member object for the specified user. func (c *Client) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) { var m *discord.Member return m, c.RequestJSON(&m, "GET", EndpointGuilds+guildID.String()+"/members/"+userID.String()) } // Members returns a list of members of the guild with the passed id. This // method automatically paginates until it reaches the passed limit, or, if the // limit is set to 0, has fetched all members in the guild. // // As the underlying endpoint has a maximum of 1000 members per request, at // maximum a total of limit/1000 rounded up requests will be made, although // they may be less if no more members are available. // // When fetching the members, those with the smallest ID will be fetched first. func (c *Client) Members(guildID discord.GuildID, limit uint) ([]discord.Member, error) { return c.MembersAfter(guildID, 0, limit) } // MembersAfter returns a list of members of the guild with the passed id. This // method automatically paginates until it reaches the passed limit, or, if the // limit is set to 0, has fetched all members with an id higher than after. // // As the underlying endpoint has a maximum of 1000 members per request, at // maximum a total of limit/1000 rounded up requests will be made, although // they may be less, if no more members are available. func (c *Client) MembersAfter( guildID discord.GuildID, after discord.UserID, limit uint) ([]discord.Member, error) { mems := make([]discord.Member, 0, limit) fetch := uint(maxMemberFetchLimit) unlimited := limit == 0 for limit > 0 || unlimited { // Only fetch as much as we need. Since limit gradually decreases, // we only need to fetch min(fetch, limit). if limit > 0 { if fetch > limit { fetch = limit } limit -= fetch } m, err := c.membersAfter(guildID, after, fetch) if err != nil { return mems, err } mems = append(mems, m...) // There aren't any to fetch, even if this is less than limit. if len(m) < maxMemberFetchLimit { break } after = mems[len(mems)-1].User.ID } if len(mems) == 0 { return nil, nil } return mems, nil } func (c *Client) membersAfter( guildID discord.GuildID, after discord.UserID, limit uint) ([]discord.Member, error) { switch { case limit == 0: limit = 0 case limit > 1000: limit = 1000 } var param struct { After discord.UserID `schema:"after,omitempty"` Limit uint `schema:"limit"` } param.Limit = limit param.After = after var mems []discord.Member return mems, c.RequestJSON( &mems, "GET", EndpointGuilds+guildID.String()+"/members", httputil.WithSchema(c, param), ) } // https://discord.com/developers/docs/resources/guild#add-guild-member-json-params type AddMemberData struct { // Token is an oauth2 access token granted with the guilds.join to the // bot's application for the user you want to add to the guild. Token string `json:"access_token"` // Nick is the value to set users nickname to. // // Requires MANAGE_NICKNAMES. Nick option.String `json:"nick,omitempty"` // Roles is an array of role ids the member is assigned. // // Requires MANAGE_ROLES. Roles *[]discord.RoleID `json:"roles,omitempty"` // Mute specifies whether the user is muted in voice channels. // // Requires MUTE_MEMBERS. Mute option.Bool `json:"mute,omitempty"` // Deaf specifies whether the user is deafened in voice channels. // // Requires DEAFEN_MEMBERS. Deaf option.Bool `json:"deaf,omitempty"` } // AddMember adds a user to the guild, provided you have a valid oauth2 access // token for the user with the guilds.join scope. Returns a 201 Created with // the guild member as the body, or 204 No Content if the user is already a // member of the guild. // // Fires a Guild Member Add Gateway event. // // The Authorization header must be a Bot token (belonging to the same // application used for authorization), and the bot must be a member of the // guild with CREATE_INSTANT_INVITE permission. func (c *Client) AddMember( guildID discord.GuildID, userID discord.UserID, data AddMemberData) (*discord.Member, error) { var mem *discord.Member return mem, c.RequestJSON( &mem, "PUT", EndpointGuilds+guildID.String()+"/members/"+userID.String(), httputil.WithJSONBody(data), ) } // https://discord.com/developers/docs/resources/guild#add-guild-member-json-params type ModifyMemberData struct { // Nick is the value to set users nickname to. // // Requires MANAGE_NICKNAMES. Nick option.String `json:"nick,omitempty"` // Roles is an array of role ids the member is assigned. // // Requires MANAGE_ROLES. Roles *[]discord.RoleID `json:"roles,omitempty"` // Mute specifies whether the user is muted in voice channels. // // Requires MUTE_MEMBERS. Mute option.Bool `json:"mute,omitempty"` // Deaf specifies whether the user is deafened in voice channels. // // Requires DEAFEN_MEMBERS. Deaf option.Bool `json:"deaf,omitempty"` // Voice channel is the id of channel to move user to (if they are // connected to voice). // // Requires MOVE_MEMBER VoiceChannel discord.ChannelID `json:"channel_id,omitempty"` } // ModifyMember modifies attributes of a guild member. If the channel_id is set // to null, this will force the target user to be disconnected from voice. // // Fires a Guild Member Update Gateway event. func (c *Client) ModifyMember(guildID discord.GuildID, userID discord.UserID, data ModifyMemberData) error { return c.FastRequest( "PATCH", EndpointGuilds+guildID.String()+"/members/"+userID.String(), httputil.WithJSONBody(data), ) } // https://discord.com/developers/docs/resources/guild#get-guild-prune-count-query-string-params type PruneCountData struct { // Days is the number of days to count prune for (1 or more, default 7). Days uint `schema:"days"` // IncludedRoles are the role(s) to include. IncludedRoles []discord.RoleID `schema:"include_roles,omitempty"` } // PruneCount returns the number of members that would be removed in a prune // operation. Days must be 1 or more, default 7. // // By default, prune will not remove users with roles. You can optionally // include specific roles in your prune by providing the IncludedRoles // parameter. Any inactive user that has a subset of the provided role(s) // will be counted in the prune and users with additional roles will not. // // Requires KICK_MEMBERS. func (c *Client) PruneCount(guildID discord.GuildID, data PruneCountData) (uint, error) { if data.Days == 0 { data.Days = 7 } var resp struct { Pruned uint `json:"pruned"` } return resp.Pruned, c.RequestJSON( &resp, "GET", EndpointGuilds+guildID.String()+"/prune", httputil.WithSchema(c, data), ) } // https://discord.com/developers/docs/resources/guild#begin-guild-prune-query-string-params type PruneData struct { // Days is the number of days to prune (1 or more, default 7). Days uint `schema:"days"` // ReturnCount specifies whether 'pruned' is returned. Discouraged for // large guilds. ReturnCount bool `schema:"compute_prune_count"` // IncludedRoles are the role(s) to include. IncludedRoles []discord.RoleID `schema:"include_roles,omitempty"` } // Prune begins a prune. Days must be 1 or more, default 7. // // By default, prune will not remove users with roles. You can optionally // include specific roles in your prune by providing the IncludedRoles // parameter. Any inactive user that has a subset of the provided role(s) // will be included in the prune and users with additional roles will not. // // Requires KICK_MEMBERS. // Fires multiple Guild Member Remove Gateway events. func (c *Client) Prune(guildID discord.GuildID, data PruneData) (uint, error) { if data.Days == 0 { data.Days = 7 } var resp struct { Pruned uint `json:"pruned"` } return resp.Pruned, c.RequestJSON( &resp, "POST", EndpointGuilds+guildID.String()+"/prune", httputil.WithSchema(c, data), ) } // Kick removes a member from a guild. // // Requires KICK_MEMBERS permission. // Fires a Guild Member Remove Gateway event. func (c *Client) Kick(guildID discord.GuildID, userID discord.UserID) error { return c.KickWithReason(guildID, userID, "") } // KickWithReason removes a member from a guild. // The reason, if non-empty, will be displayed in the audit log of the guild. // // Requires KICK_MEMBERS permission. // Fires a Guild Member Remove Gateway event. func (c *Client) KickWithReason( guildID discord.GuildID, userID discord.UserID, reason string) error { var data struct { Reason string `schema:"reason,omitempty"` } data.Reason = reason return c.FastRequest( "DELETE", EndpointGuilds+guildID.String()+"/members/"+userID.String(), httputil.WithSchema(c, data), ) } // Bans returns a list of ban objects for the users banned from this guild. // // Requires the BAN_MEMBERS permission. func (c *Client) Bans(guildID discord.GuildID) ([]discord.Ban, error) { var bans []discord.Ban return bans, c.RequestJSON( &bans, "GET", EndpointGuilds+guildID.String()+"/bans", ) } // GetBan returns a ban object for the given user. // // Requires the BAN_MEMBERS permission. func (c *Client) GetBan(guildID discord.GuildID, userID discord.UserID) (*discord.Ban, error) { var ban *discord.Ban return ban, c.RequestJSON( &ban, "GET", EndpointGuilds+guildID.String()+"/bans/"+userID.String(), ) } // https://discord.com/developers/docs/resources/guild#create-guild-ban-query-string-params type BanData struct { // DeleteDays is the number of days to delete messages for (0-7). DeleteDays option.Uint `schema:"delete_message_days,omitempty"` // Reason is the reason for the ban. Reason option.String `schema:"reason,omitempty"` } // Ban creates a guild ban, and optionally delete previous messages sent by the // banned user. // // Requires the BAN_MEMBERS permission. func (c *Client) Ban(guildID discord.GuildID, userID discord.UserID, data BanData) error { return c.FastRequest( "PUT", EndpointGuilds+guildID.String()+"/bans/"+userID.String(), httputil.WithSchema(c, data), ) } // Unban removes the ban for a user. // // Requires the BAN_MEMBERS permissions. // Fires a Guild Ban Remove Gateway event. func (c *Client) Unban(guildID discord.GuildID, userID discord.UserID) error { return c.FastRequest("DELETE", EndpointGuilds+guildID.String()+"/bans/"+userID.String()) }