/* eslint-disable no-useless-escape */ class Resolver { constructor(client) { this.client = client; } components(str = '', type, exact = true) { const string = str.toLowerCase(); const components = this.client.registry.components .filter((c) => c.type === type) .filter(exact ? filterExact(string) : filterInexact(string)) //eslint-disable-line no-use-before-define .array(); return components || []; } resolveBoolean(input) { input = input.toLowerCase(); const truthy = [ 'on', 'true', 'yes', 'enable', 'y' ]; const falsey = [ 'off', 'false', 'no', 'disable', 'n' ]; if(truthy.includes(input)) { return true; } else if (falsey.includes(input)) { return false; } return null; } /** * Resolves methods used primarily for settings, also deals with appending the arguments into existing lists * * @param {Array} args The incoming arguments with the first element being the method ex. ['add','ban','kick'] * @param {Array} valid An array of items to compare to, if an argument doesn't exist in this array it'll be skipped over * @param {Array} [existing=[]] Existing values in the array, valid elements will be appended to this * @returns {Object} * @memberof Resolver */ resolveMethod(args, valid, existing = []) { const methods = { list: ['view', 'list', '?'], add: ['add', '+'], remove: ['remove', 'delete', '-'] }; if (!args.length) return false; // eslint-disable-next-line prefer-const let [method, ...rest] = args; method = method.toLowerCase(); if (!rest.length) { if (methods.list.includes(method)) return { method: 'list', rest }; if (methods.add.includes(method)) return { method: 'add', rest }; if (methods.remove.includes(method)) return { method: 'remove', rest }; } if (methods.list.includes(method)) { return { method: 'list' }; } else if (methods.add.includes(method)) { const added = []; for (let elem of rest) { elem = elem.toLowerCase(); if (existing.includes(elem) || valid && !valid.includes(elem)) continue; added.push(elem); existing.push(elem); } return { rest, method: 'add', changed: added, result: existing }; } else if (methods.remove.includes(method)) { const removed = []; for (let elem of rest) { elem = elem.toLowerCase(); if (!existing.includes(elem) || removed.includes(elem) || valid && !valid.includes(elem)) continue; removed.push(elem); existing.splice(existing.indexOf(elem), 1); } return { rest, method: 'remove', changed: removed, result: existing }; } return false; } async resolveMemberAndUser(string, guild) { const str = string.toLowerCase(); const index = guild ? guild.members.cache : this.client.users.cache; let member = null; if((/<@!?(\d{17,21})>/iyu).test(str)) { //mentions const matches = (/<@!?(\d{17,21})>/iuy).exec(str); member = index.get(matches[1]); if(!member) { try { member = await index.fetch(matches[1]); } catch(e) { try { member = await this.client.users.cache.fetch(matches[1]); } catch(e) {} //eslint-disable-line no-empty } //eslint-disable-line no-empty } } else if((/\d{17,21}/iuy).test(str)) { //id const matches = (/(\d{17,21})/iuy).exec(str); member = index.get(matches[1]); if(!member) { try { member = await index.fetch(matches[1]); } catch(e) { try { member = await this.client.users.cache.fetch(matches[1]); } catch(e) {} //eslint-disable-line no-empty } //eslint-disable-line no-empty } } else if((/(.{2,32})#(\d{4})/iuy).test(str)) { //username#discrim const matches = (/(.{2,32})#(\d{4})/iuy).exec(str); member = guild ? guild.members.cache.filter((m) => m.user.username === matches[1] && m.user.discriminator === matches[2]).first() : this.client.users.cache.filter((u) => u.username === matches[1] && u.discriminator === matches[2]).first(); } return member || null; } /** * Resolve several user resolveables * * @param {array} [resolveables=[]] an array of user resolveables (name, id, tag) * @param {boolean} [strict=false] whether or not to attempt resolving by partial usernames * @returns {array || boolean} Array of resolved users or false if none were resolved * @memberof Resolver */ async resolveUsers(resolveables = [], strict = false) { if(typeof resolveables === 'string') resolveables = [ resolveables ]; if(resolveables.length === 0) return false; const { users } = this.client; const resolved = []; for(const resolveable of resolveables) { if((/<@!?([0-9]{17,21})>/u).test(resolveable)) { const [, id] = resolveable.match(/<@!?([0-9]{17,21})>/u); const user = await users.fetch(id).catch((err) => { if(err.code === 10013) return false; this.client.logger.warn(err); return false; }); if(user) resolved.push(user); } else if((/(id:)?([0-9]{17,21})/u).test(resolveable)) { const [,, id] = resolveable.match(/(id:)?([0-9]{17,21})/u); const user = await users.fetch(id).catch((err) => { if(err.code === 10013) return false; this.client.logger.warn(err); return false; }); if(user) resolved.push(user); } else if((/^@?([\S\s]{1,32})#([0-9]{4})/u).test(resolveable)) { const m = resolveable.match(/^@?([\S\s]{1,32})#([0-9]{4})/u); const username = m[1].toLowerCase(); const discrim = m[2].toLowerCase(); const user = users.cache.filter((u) => u.username.toLowerCase() === username && u.discriminator === discrim).first(); if(user) resolved.push(user); } else if(!strict) { const name = resolveable.toLowerCase(); const user = users.cache.filter((u) => u.username.toLowerCase().includes(name)).first(); if(user) resolved.push(user); } } return resolved.length ? resolved : false; } async resolveUser(resolveable, strict) { if (!resolveable) return false; const result = await this.resolveUsers([ resolveable ], strict); return result ? result[0] : false; } /** * Resolve multiple member resolveables * * @param {array} [resolveables=[]] an array of member resolveables (name, nickname, tag, id) * @param {boolean} [strict=false] whether or not to attempt resolving by partial matches * @param {Guild} guild the guild in which to look for members * @returns {array || boolean} an array of resolved members or false if none were resolved * @memberof Resolver */ async resolveMembers(resolveables = [], guild, strict = false) { if(typeof resolveables === 'string') resolveables = [ resolveables ]; if(resolveables.length === 0) return false; const { members } = guild; const resolved = []; for(const resolveable of resolveables) { if((/<@!?([0-9]{17,21})>/u).test(resolveable)) { const [, id] = resolveable.match(/<@!?([0-9]{17,21})>/u); const member = await members.fetch(id).catch((err) => { if(err.code === 10007) return false; this.client.logger.warn(err); return false; }); if(member) resolved.push(member); } else if((/(id:)?([0-9]{17,21})/u).test(resolveable)) { const [,, id] = resolveable.match(/(id:)?([0-9]{17,21})/u); const member = await members.fetch(id).catch((err) => { if(err.code === 10007) return false; this.client.logger.warn(err); return false; }); if(member) resolved.push(member); } else if((/^@?([\S\s]{1,32})#([0-9]{4})/u).test(resolveable)) { const m = resolveable.match(/^@?([\S\s]{1,32})#([0-9]{4})/u); const username = m[1].toLowerCase(); const discrim = m[2].toLowerCase(); const member = members.cache.filter((m) => m.user.username.toLowerCase() === username && m.user.discriminator === discrim).first(); if(member) resolved.push(member); } else if((/^@?([\S\s]{1,32})/u).test(resolveable) && guild && !strict) { const nickname = resolveable.match(/^@?([\S\s]{1,32})/u)[0].toLowerCase(); const member = members.cache.filter((m) => m && m.user && ((!m.nickname ? false : m.nickname.toLowerCase() === nickname) || (!m.nickname ? false : m.nickname.toLowerCase().includes(nickname)) || m.user.username.toLowerCase().includes(nickname) || m.user.username.toLowerCase() === nickname)).first(); if(member) resolved.push(member); } } return resolved.length > 0 ? resolved : false; } async resolveMember(resolveable, guild, strict) { if (!resolveable) return false; const result = await this.resolveMembers([ resolveable ], guild, strict); return result ? result[0] : false; } /** * Resolve multiple channels * * @param {array} [resolveables=[]] an array of channel resolveables (name, id) * @param {guild} guild the guild in which to look for channels * @param {boolean} [strict=false] whether or not partial names are resolved * @returns {array || false} an array of guild channels or false if none were resolved * @memberof Resolver */ resolveChannels(resolveables = [], guild, strict = false) { if(typeof resolveables === 'string') resolveables = [ resolveables ]; if(resolveables.length === 0) return false; const { channels } = guild; const resolved = []; for(const resolveable of resolveables) { const channel = channels.resolve(resolveable); if(channel) { resolved.push(channel); continue; } const name = /^#?([a-z0-9\-_0]*)/iu; const id = /^<#([0-9]*)>/iu; if (id.test(resolveable)) { const match = resolveable.match(id); const [, ch] = match; const channel = channels.resolve(ch); if (channel) resolved.push(channel); } else if (name.test(resolveable)) { const match = resolveable.match(name); const ch = match[1].toLowerCase(); const [ channel ] = channels.cache.filter((c) => { if(!strict) return c.name.toLowerCase().includes(ch); return c.name.toLowerCase() === ch; }).first(1); if(channel) resolved.push(channel); } } return resolved.length > 0 ? resolved : false; } resolveChannel(resolveable, guild, strict) { if (!resolveable) return false; const result = this.resolveChannels([resolveable], guild, strict); return result ? result[0] : false; } /** * Resolve multiple roles * * @param {array} [resolveables=[]] an array of roles resolveables (name, id) * @param {Guild} guild the guild in which to look for roles * @param {boolean} [strict=false] whether or not partial names are resolved * @returns {array || false} an array of roles or false if none were resolved * @memberof Resolver */ async resolveRoles(resolveables = [], guild, strict = false) { if(typeof resolveables === 'string') resolveables = [ resolveables ]; if(resolveables.length === 0) return false; const { roles } = guild; const resolved = []; for(const resolveable of resolveables) { const id = /^(<@&)?([0-9]{16,22})>?/iu; if(id.test(resolveable)) { const match = resolveable.match(id); const [,, rId] = match; const role = await roles.fetch(rId).catch(this.client.logger.error); if(role) resolved.push(role); } else { const role = roles.cache.filter((r) => { if(!strict) return r.name.toLowerCase().includes(resolveable.toLowerCase()); return r.name.toLowerCase() === resolveable.toLowerCase(); }).first(); if(role) resolved.push(role); } } return resolved.length > 0 ? resolved : false; } async resolveRole(resolveable, guild, strict) { if (!resolveable) return false; const result = await this.resolveRoles([resolveable], guild, strict); return result ? result[0] : false; } } module.exports = Resolver; const filterExact = (search) => (comp) => comp.id.toLowerCase() === search || comp.resolveable.toLowerCase() === search || comp.aliases && (comp.aliases.some((ali) => `${comp.type}:${ali}`.toLowerCase() === search) || comp.aliases.some((ali) => ali.toLowerCase() === search)); const filterInexact = (search) => (comp) => comp.id.toLowerCase().includes(search) || comp.resolveable.toLowerCase().includes(search) || comp.aliases && (comp.aliases.some((ali) => `${comp.type}:${ali}`.toLowerCase().includes(search)) || comp.aliases.some((ali) => ali.toLowerCase().includes(search)));