GuildMemberManager.js (10046B)
1 'use strict'; 2 3 const BaseManager = require('./BaseManager'); 4 const { Error, TypeError } = require('../errors'); 5 const GuildMember = require('../structures/GuildMember'); 6 const Collection = require('../util/Collection'); 7 const { Events, OPCodes } = require('../util/Constants'); 8 9 /** 10 * Manages API methods for GuildMembers and stores their cache. 11 * @extends {BaseManager} 12 */ 13 class GuildMemberManager extends BaseManager { 14 constructor(guild, iterable) { 15 super(guild.client, iterable, GuildMember); 16 /** 17 * The guild this manager belongs to 18 * @type {Guild} 19 */ 20 this.guild = guild; 21 } 22 23 /** 24 * The cache of this Manager 25 * @type {Collection<Snowflake, GuildMember>} 26 * @name GuildMemberManager#cache 27 */ 28 29 add(data, cache = true) { 30 return super.add(data, cache, { id: data.user.id, extras: [this.guild] }); 31 } 32 33 /** 34 * Data that resolves to give a GuildMember object. This can be: 35 * * A GuildMember object 36 * * A User resolvable 37 * @typedef {GuildMember|UserResolvable} GuildMemberResolvable 38 */ 39 40 /** 41 * Resolves a GuildMemberResolvable to a GuildMember object. 42 * @param {GuildMemberResolvable} member The user that is part of the guild 43 * @returns {?GuildMember} 44 */ 45 resolve(member) { 46 const memberResolvable = super.resolve(member); 47 if (memberResolvable) return memberResolvable; 48 const userResolvable = this.client.users.resolveID(member); 49 if (userResolvable) return super.resolve(userResolvable); 50 return null; 51 } 52 53 /** 54 * Resolves a GuildMemberResolvable to a member ID string. 55 * @param {GuildMemberResolvable} member The user that is part of the guild 56 * @returns {?Snowflake} 57 */ 58 resolveID(member) { 59 const memberResolvable = super.resolveID(member); 60 if (memberResolvable) return memberResolvable; 61 const userResolvable = this.client.users.resolveID(member); 62 return this.cache.has(userResolvable) ? userResolvable : null; 63 } 64 65 /** 66 * Options used to fetch a single member from a guild. 67 * @typedef {Object} FetchMemberOptions 68 * @property {UserResolvable} user The user to fetch 69 * @property {boolean} [cache=true] Whether or not to cache the fetched member 70 */ 71 72 /** 73 * Options used to fetch multiple members from a guild. 74 * @typedef {Object} FetchMembersOptions 75 * @property {UserResolvable|UserResolvable[]} user The user(s) to fetch 76 * @property {?string} query Limit fetch to members with similar usernames 77 * @property {number} [limit=0] Maximum number of members to request 78 * @property {boolean} [withPresences=false] Whether or not to include the presences 79 * @property {number} [time=120e3] Timeout for receipt of members 80 */ 81 82 /** 83 * Fetches member(s) from Discord, even if they're offline. 84 * @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] If a UserResolvable, the user to fetch. 85 * If undefined, fetches all members. 86 * If a query, it limits the results to users with similar usernames. 87 * @returns {Promise<GuildMember>|Promise<Collection<Snowflake, GuildMember>>} 88 * @example 89 * // Fetch all members from a guild 90 * guild.members.fetch() 91 * .then(console.log) 92 * .catch(console.error); 93 * @example 94 * // Fetch a single member 95 * guild.members.fetch('66564597481480192') 96 * .then(console.log) 97 * .catch(console.error); 98 * @example 99 * // Fetch a single member without caching 100 * guild.members.fetch({ user, cache: false }) 101 * .then(console.log) 102 * .catch(console.error); 103 * @example 104 * // Fetch by an array of users including their presences 105 * guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true }) 106 * .then(console.log) 107 * .catch(console.error); 108 * @example 109 * // Fetch by query 110 * guild.members.fetch({ query: 'hydra', limit: 1 }) 111 * .then(console.log) 112 * .catch(console.error); 113 */ 114 fetch(options) { 115 if (!options) return this._fetchMany(); 116 const user = this.client.users.resolveID(options); 117 if (user) return this._fetchSingle({ user, cache: true }); 118 if (options.user) { 119 if (Array.isArray(options.user)) { 120 options.user = options.user.map(u => this.client.users.resolveID(u)); 121 return this._fetchMany(options); 122 } else { 123 options.user = this.client.users.resolveID(options.user); 124 } 125 if (!options.limit && !options.withPresences) return this._fetchSingle(options); 126 } 127 return this._fetchMany(options); 128 } 129 130 /** 131 * Prunes members from the guild based on how long they have been inactive. 132 * <info>It's recommended to set options.count to `false` for large guilds.</info> 133 * @param {Object} [options] Prune options 134 * @param {number} [options.days=7] Number of days of inactivity required to kick 135 * @param {boolean} [options.dry=false] Get number of users that will be kicked, without actually kicking them 136 * @param {boolean} [options.count=true] Whether or not to return the number of users that have been kicked. 137 * @param {string} [options.reason] Reason for this prune 138 * @returns {Promise<number|null>} The number of members that were/will be kicked 139 * @example 140 * // See how many members will be pruned 141 * guild.members.prune({ dry: true }) 142 * .then(pruned => console.log(`This will prune ${pruned} people!`)) 143 * .catch(console.error); 144 * @example 145 * // Actually prune the members 146 * guild.members.prune({ days: 1, reason: 'too many people!' }) 147 * .then(pruned => console.log(`I just pruned ${pruned} people!`)) 148 * .catch(console.error); 149 */ 150 prune({ days = 7, dry = false, count = true, reason } = {}) { 151 if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE'); 152 return this.client.api 153 .guilds(this.guild.id) 154 .prune[dry ? 'get' : 'post']({ 155 query: { 156 days, 157 compute_prune_count: count, 158 }, 159 reason, 160 }) 161 .then(data => data.pruned); 162 } 163 164 /** 165 * Bans a user from the guild. 166 * @param {UserResolvable} user The user to ban 167 * @param {Object} [options] Options for the ban 168 * @param {number} [options.days=0] Number of days of messages to delete 169 * @param {string} [options.reason] Reason for banning 170 * @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible. 171 * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot 172 * be resolved, the user ID will be the result. 173 * @example 174 * // Ban a user by ID (or with a user/guild member object) 175 * guild.members.ban('84484653687267328') 176 * .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`)) 177 * .catch(console.error); 178 */ 179 ban(user, options = { days: 0 }) { 180 if (options.days) options['delete-message-days'] = options.days; 181 const id = this.client.users.resolveID(user); 182 if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true)); 183 return this.client.api 184 .guilds(this.guild.id) 185 .bans[id].put({ query: options }) 186 .then(() => { 187 if (user instanceof GuildMember) return user; 188 const _user = this.client.users.resolve(id); 189 if (_user) { 190 const member = this.resolve(_user); 191 return member || _user; 192 } 193 return id; 194 }); 195 } 196 197 /** 198 * Unbans a user from the guild. 199 * @param {UserResolvable} user The user to unban 200 * @param {string} [reason] Reason for unbanning user 201 * @returns {Promise<User>} 202 * @example 203 * // Unban a user by ID (or with a user/guild member object) 204 * guild.members.unban('84484653687267328') 205 * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) 206 * .catch(console.error); 207 */ 208 unban(user, reason) { 209 const id = this.client.users.resolveID(user); 210 if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID')); 211 return this.client.api 212 .guilds(this.guild.id) 213 .bans[id].delete({ reason }) 214 .then(() => this.client.users.resolve(user)); 215 } 216 217 _fetchSingle({ user, cache }) { 218 const existing = this.cache.get(user); 219 if (existing && !existing.partial) return Promise.resolve(existing); 220 return this.client.api 221 .guilds(this.guild.id) 222 .members(user) 223 .get() 224 .then(data => this.add(data, cache)); 225 } 226 227 _fetchMany({ limit = 0, withPresences: presences = false, user: user_ids, query, time = 120e3 } = {}) { 228 return new Promise((resolve, reject) => { 229 if (this.guild.memberCount === this.cache.size && !query && !limit && !presences && !user_ids) { 230 resolve(this.cache); 231 return; 232 } 233 if (!query && !user_ids) query = ''; 234 this.guild.shard.send({ 235 op: OPCodes.REQUEST_GUILD_MEMBERS, 236 d: { 237 guild_id: this.guild.id, 238 presences, 239 user_ids, 240 query, 241 limit, 242 }, 243 }); 244 const fetchedMembers = new Collection(); 245 const option = query || limit || presences || user_ids; 246 const handler = (members, guild) => { 247 if (guild.id !== this.guild.id) return; 248 timeout.refresh(); 249 for (const member of members.values()) { 250 if (option) fetchedMembers.set(member.id, member); 251 } 252 if ( 253 this.guild.memberCount <= this.cache.size || 254 (option && members.size < 1000) || 255 (limit && fetchedMembers.size >= limit) 256 ) { 257 this.guild.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler); 258 let fetched = option ? fetchedMembers : this.cache; 259 if (user_ids && !Array.isArray(user_ids) && fetched.size) fetched = fetched.first(); 260 resolve(fetched); 261 } 262 }; 263 const timeout = this.guild.client.setTimeout(() => { 264 this.guild.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler); 265 reject(new Error('GUILD_MEMBERS_TIMEOUT')); 266 }, time); 267 this.guild.client.on(Events.GUILD_MEMBERS_CHUNK, handler); 268 }); 269 } 270 } 271 272 module.exports = GuildMemberManager;