Skip to content

Commit

Permalink
feat(Managers): ✨ Add GuildInviteManager (#5889)
Browse files Browse the repository at this point in the history
Co-authored-by: iShibi <shubhamparihar391@gmail.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <SpaceEEC@users.noreply.github.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
Co-authored-by: iCrawl <icrawltogo@gmail.com>
  • Loading branch information
8 people committed Jul 4, 2021
1 parent c4aa9fe commit 9e08b02
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 69 deletions.
4 changes: 2 additions & 2 deletions src/client/actions/InviteCreate.js
@@ -1,7 +1,6 @@
'use strict';

const Action = require('./Action');
const Invite = require('../../structures/Invite');
const { Events } = require('../../util/Constants');

class InviteCreateAction extends Action {
Expand All @@ -12,7 +11,8 @@ class InviteCreateAction extends Action {
if (!channel) return false;

const inviteData = Object.assign(data, { channel, guild });
const invite = new Invite(client, inviteData);
const invite = guild.invites.add(inviteData);

/**
* Emitted when an invite is created.
* <info> This event only triggers if the client has `MANAGE_GUILD` permissions for the guild,
Expand Down
1 change: 1 addition & 0 deletions src/client/actions/InviteDelete.js
Expand Up @@ -13,6 +13,7 @@ class InviteDeleteAction extends Action {

const inviteData = Object.assign(data, { channel, guild });
const invite = new Invite(client, inviteData);
guild.invites.cache.delete(invite.code);

/**
* Emitted when an invite is deleted.
Expand Down
4 changes: 4 additions & 0 deletions src/errors/Messages.js
Expand Up @@ -113,6 +113,10 @@ const Messages = {

VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.',

INVITE_RESOLVE_CODE: 'Could not resolve the code to fetch the invite.',

INVITE_NOT_FOUND: 'Could not find the requested invite.',

DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them",

Expand Down
199 changes: 199 additions & 0 deletions src/managers/GuildInviteManager.js
@@ -0,0 +1,199 @@
'use strict';

const BaseManager = require('./BaseManager');
const { Error } = require('../errors');
const Invite = require('../structures/Invite');
const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');

/**
* Manages API methods for GuildInvites and stores their cache.
* @extends {BaseManager}
*/
class GuildInviteManager extends BaseManager {
constructor(guild, iterable) {
super(guild.client, iterable, Invite);

/**
* The guild this Manager belongs to
* @type {Guild}
*/
this.guild = guild;
}

/**
* The cache of this Manager
* @type {Collection<Snowflake, Invite>}
* @name GuildInviteManager#cache
*/

add(data, cache) {
return super.add(data, cache, { id: data.code, extras: [this.guild] });
}

/**
* Data that resolves to give an Invite object. This can be:
* * An invite code
* * An invite URL
* @typedef {string} InviteResolvable
*/

/**
* Resolves an InviteResolvable to an Invite object.
* @method resolve
* @memberof GuildInviteManager
* @instance
* @param {InviteResolvable} invite The invite resolvable to resolve
* @returns {?Invite}
*/

/**
* Resolves an InviteResolvable to an invite code string.
* @method resolveId
* @memberof GuildInviteManager
* @instance
* @param {InviteResolvable} invite The invite resolvable to resolve
* @returns {?string}
*/

/**
* Options used to fetch a single invite from a guild.
* @typedef {Object} FetchInviteOptions
* @property {InviteResolvable} code The invite to fetch
* @property {boolean} [cache=true] Whether or not to cache the fetched invite
* @property {boolean} [force=false] Whether to skip the cache check and request the API
*/

/**
* Options used to fetch all invites from a guild.
* @typedef {Object} FetchInvitesOptions
* @property {boolean} cache Whether or not to cache the fetched invites
*/

/**
* Fetches invite(s) from Discord.
* @param {InviteResolvable|FetchInviteOptions|FetchInvitesOptions} [options] Options for fetching guild invite(s)
* @returns {Promise<Invite|Collection<Snowflake, Invite>>}
* @example
* // Fetch all invites from a guild
* guild.invites.fetch()
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch all invites from a guild without caching
* guild.invites.fetch({ cache: false })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch all invites from a channel
* guild.invites.fetch({ channelID, '222197033908436994' })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single invite
* guild.invites.fetch('bRCvFy9')
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single invite without checking cache
* guild.invites.fetch({ code: 'bRCvFy9', force: true })
* .then(console.log)
* .catch(console.error)
* @example
* // Fetch a single invite without caching
* guild.invites.fetch({ code: 'bRCvFy9', cache: false })
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
if (!options) return this._fetchMany();
if (typeof options === 'string') {
const code = DataResolver.resolveInviteCode(options);
if (!code) return Promise.reject(new Error('INVITE_RESOLVE_CODE'));
return this._fetchSingle({ code, cache: true });
}
if (!options.code) {
if (options.channelId) {
const id = this.guild.channels.resolveId(options.channelId);
if (!id) return Promise.reject(new Error('GUILD_CHANNEL_RESOLVE'));
return this._fetchChannelMany(id, options.cache);
}

if ('cache' in options) return this._fetchMany(options.cache);
return Promise.reject(new Error('INVITE_RESOLVE_CODE'));
}
return this._fetchSingle({
...options,
code: DataResolver.resolveInviteCode(options.code),
});
}

async _fetchSingle({ code, cache, force = false }) {
if (!force) {
const existing = this.cache.get(code);
if (existing) return existing;
}

const invites = await this._fetchMany(cache);
const invite = invites.get(code);
if (!invite) throw new Error('INVITE_NOT_FOUND');
return invite;
}

async _fetchMany(cache) {
const data = await this.client.api.guilds(this.guild.id).invites.get();
return data.reduce((col, invite) => col.set(invite.code, this.add(invite, cache)), new Collection());
}

async _fetchChannelMany(channelID, cache) {
const data = await this.client.api.channels(channelID).invites.get();
return data.reduce((col, invite) => col.set(invite.code, this.add(invite, cache)), new Collection());
}

/**
* Create an invite to the guild from the provided channel.
* @param {GuildChannelResolvable} channel The options for creating the invite from a channel.
* @param {CreateInviteOptions} [options={}] The options for creating the invite from a channel.
* @returns {Promise<Invite>}
* @example
* // Create an invite to a selected channel
* guild.invites.create('599942732013764608')
* .then(console.log)
* .catch(console.error);
*/
async create(
channel,
{ temporary = false, maxAge = 86400, maxUses = 0, unique, targetUser, targetApplication, targetType, reason } = {},
) {
const id = this.guild.channels.resolveId(channel);
if (!id) throw new Error('GUILD_CHANNEL_RESOLVE');

const invite = await this.client.api.channels(id).invites.post({
data: {
temporary,
max_age: maxAge,
max_uses: maxUses,
unique,
target_user_id: this.client.users.resolveId(targetUser),
target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication,
target_type: targetType,
},
reason,
});
return new Invite(this.client, invite);
}

/**
* Deletes an invite.
* @param {InviteResolvable} invite The invite to delete
* @param {string} [reason] Reason for deleting the invite
* @returns {Promise<void>}
*/
async delete(invite, reason) {
const code = DataResolver.resolveInviteCode(invite);

await this.client.api.invites(code).delete({ reason });
}
}

module.exports = GuildInviteManager;
30 changes: 0 additions & 30 deletions src/structures/Guild.js
Expand Up @@ -5,7 +5,6 @@ const GuildAuditLogs = require('./GuildAuditLogs');
const GuildPreview = require('./GuildPreview');
const GuildTemplate = require('./GuildTemplate');
const Integration = require('./Integration');
const Invite = require('./Invite');
const Webhook = require('./Webhook');
const WelcomeScreen = require('./WelcomeScreen');
const { Error, TypeError } = require('../errors');
Expand Down Expand Up @@ -586,35 +585,6 @@ class Guild extends AnonymousGuild {
.then(data => new GuildTemplate(this.client, data));
}

/**
* Fetches a collection of invites to this guild.
* Resolves with a collection mapping invites by their codes.
* @returns {Promise<Collection<string, Invite>>}
* @example
* // Fetch invites
* guild.fetchInvites()
* .then(invites => console.log(`Fetched ${invites.size} invites`))
* .catch(console.error);
* @example
* // Fetch invite creator by their id
* guild.fetchInvites()
* .then(invites => console.log(invites.find(invite => invite.inviter.id === '84484653687267328')))
* .catch(console.error);
*/
fetchInvites() {
return this.client.api
.guilds(this.id)
.invites.get()
.then(inviteItems => {
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.client, inviteItem);
invites.set(invite.code, invite);
}
return invites;
});
}

/**
* Obtains a guild preview for this guild from Discord.
* @returns {Promise<GuildPreview>}
Expand Down
2 changes: 1 addition & 1 deletion src/structures/GuildAuditLogs.js
Expand Up @@ -475,7 +475,7 @@ class GuildAuditLogsEntry {
if (me.permissions.has(Permissions.FLAGS.MANAGE_GUILD)) {
let change = this.changes.find(c => c.key === 'code');
change = change.new ?? change.old;
return guild.fetchInvites().then(invites => {
return guild.invites.fetch().then(invites => {
this.target = invites.find(i => i.code === change);
});
} else {
Expand Down
39 changes: 5 additions & 34 deletions src/structures/GuildChannel.js
@@ -1,7 +1,6 @@
'use strict';

const Channel = require('./Channel');
const Invite = require('./Invite');
const PermissionOverwrites = require('./PermissionOverwrites');
const { Error } = require('../errors');
const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager');
Expand Down Expand Up @@ -470,46 +469,18 @@ class GuildChannel extends Channel {
* .then(invite => console.log(`Created an invite with a code of ${invite.code}`))
* .catch(console.error);
*/
createInvite({
temporary = false,
maxAge = 86400,
maxUses = 0,
unique,
targetUser,
targetApplication,
targetType,
reason,
} = {}) {
return this.client.api
.channels(this.id)
.invites.post({
data: {
temporary,
max_age: maxAge,
max_uses: maxUses,
unique,
target_user_id: this.client.users.resolveId(targetUser),
target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication,
target_type: targetType,
},
reason,
})
.then(invite => new Invite(this.client, invite));
createInvite(options) {
return this.guild.invites.create(this.id, options);
}

/**
* Fetches a collection of invites to this guild channel.
* Resolves with a collection mapping invites by their codes.
* @param {boolean} [cache=true] Whether or not to cache the fetched invites
* @returns {Promise<Collection<string, Invite>>}
*/
async fetchInvites() {
const inviteItems = await this.client.api.channels(this.id).invites.get();
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.client, inviteItem);
invites.set(invite.code, invite);
}
return invites;
fetchInvites(cache = true) {
return this.guild.invites.fetch({ channelID: this.id, cache });
}

/**
Expand Down

0 comments on commit 9e08b02

Please sign in to comment.