Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Managers): ✨ Add GuildInviteManager #5889

Merged
merged 36 commits into from Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ed777ff
feat(Managers): ✨ Add GuildInviteManager
DraftProducts Jun 20, 2021
9f6f82e
types(GuildInviteManager) Fix pluralization of class name in typings
DraftProducts Jun 20, 2021
c576e4b
refactor: 💬 Adds literary corrections
DraftProducts Jun 20, 2021
f97bcc5
refactor: 🥅 Transform sync errors to Promise rejections
DraftProducts Jun 20, 2021
921c81f
fix: 🐛 Remove mutating in case of options is a frozen object
DraftProducts Jun 20, 2021
8bfd39a
types: 🏷️ Replace InviteOptions with CreateInviteOptions
DraftProducts Jun 20, 2021
3bc3be9
refactor(Errors): 🥅 Change "unknown invite" error to avoid confusion …
DraftProducts Jun 20, 2021
1890cd1
fix(Errors): Update quotes & formulation
DraftProducts Jun 21, 2021
8b727b8
docs: Provide more details in docs about invite creation
DraftProducts Jun 22, 2021
186b152
refactor: ♻️ Move channel parameter to options
DraftProducts Jun 22, 2021
ae9916d
types(options): 🏷️ Fix options types
DraftProducts Jun 22, 2021
edced3a
refactor: Change [] to ()
DraftProducts Jun 23, 2021
357dd09
style: 🚨 Lint suggest & typings
DraftProducts Jun 23, 2021
8aacbd9
refactor: Some suggested corrections on typings & typedoc
DraftProducts Jun 23, 2021
4d1cbf0
refactor: ♻️ Change return value of delete method to void
DraftProducts Jun 24, 2021
0900a51
fix: 📝 Remove uncertain return value of doc
DraftProducts Jun 24, 2021
dcacf1c
fix: 🐛 If "options" is a string, it's not needed to continue
DraftProducts Jun 24, 2021
92e74ef
perf: ⚡️ Remove double channel resolution
DraftProducts Jun 24, 2021
ddbb26d
perf: ⚡️ Use Permissions flags instead of string
DraftProducts Jun 24, 2021
e2992b6
types: 🏷️ Change GuildChannel to GuildChannelResolvable
DraftProducts Jun 24, 2021
6baef14
docs: 📝 Remove personnal invites of examples
DraftProducts Jun 25, 2021
546a73c
refactor(Guild): 🔥 Remove 'Guild#fetchInvites' method
DraftProducts Jun 27, 2021
8173ad0
refactor: ♻️ Refactor of review
DraftProducts Jun 30, 2021
a189ae6
fix: 🏷️ Fix tslint warnings
DraftProducts Jun 30, 2021
d12db15
refactor(Errors): Remove unused errors
DraftProducts Jul 3, 2021
d713efb
docs(GuildInviteManager): 📝 Correct errors in typedoc descriptions
DraftProducts Jul 3, 2021
7844337
refactor: ♻️ Remove useless type check
DraftProducts Jul 3, 2021
c735461
docs(GuildInviteManager): Simplification of typedoc return schem
DraftProducts Jul 3, 2021
bff9de7
refactor: ⚡️ Improve GuildChannel#fetchInvites with GuildInviteManage…
DraftProducts Jul 3, 2021
70423b6
docs(GuildInviteManager): 🏷️ Fix cache option typing
DraftProducts Jul 3, 2021
a1d8509
fix: 🚑 Fix breaking changes in GuildAuditLogs initialization
DraftProducts Jul 3, 2021
8bc7fd6
Merge branch 'master' into feature-invite-manager
DraftProducts Jul 3, 2021
5be49e9
feat(GuildChannel): ✨ Add cache option to GuildChannel#fetchInvites
DraftProducts Jul 3, 2021
bb0b368
Merge branch 'master' into feature-invite-manager
DraftProducts Jul 4, 2021
3135046
refactor(GuildInviteManager): ♻️ Add changes due to methods changes
DraftProducts Jul 4, 2021
bdcceb0
types(GuildInviteManager): 🏷️ Fix typings due to external changes
DraftProducts Jul 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions src/errors/Messages.js
Expand Up @@ -81,6 +81,8 @@ const Messages = {

PRUNE_DAYS_TYPE: 'Days must be a number',

GUILD_CHANNEL_INVITE: 'Could not find an available channel to create an invite.',

DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.',
GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.',
GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.',
Expand All @@ -105,6 +107,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
185 changes: 185 additions & 0 deletions src/managers/GuildInviteManager.js
@@ -0,0 +1,185 @@
'use strict';

const BaseManager = require('./BaseManager');
const { TypeError, 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 a Invite object. This can be:
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* * An invite code
* * An invite URL
* @typedef {string} InviteResolvable
*/

/**
* Resolves a InviteResolvable to a Invite object.
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @method resolve
* @memberof GuildInviteManager
* @instance
* @param {InviteResolvable} invite The invite resolvable to resolve
* @returns {?Invite}
*/

/**
* Resolves a InviteResolvable to a invite code string.
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @method resolveID
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @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>|Promise<Collection<Snowflake, Invite>>}
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @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 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) {
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
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 ('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),
});
}
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved

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

const invites = await this._fetchMany(cache);
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
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());
}

/**
* Create a invite to the guild from the provided channel.
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @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 } = {},
) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved

const id = this.guild.channels.resolveID(channel);
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
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),
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
target_application_id: targetApplication?.id ?? targetApplication?.applicationID ?? targetApplication,
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
target_type: targetType,
},
reason,
});
return new Invite(this.client, invite);
}

/**
* Deletes a invite.
DraftProducts marked this conversation as resolved.
Show resolved Hide resolved
* @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 @@ -580,35 +579,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
27 changes: 2 additions & 25 deletions src/structures/GuildChannel.js
Expand Up @@ -564,31 +564,8 @@ 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);
}

/**
Expand Down
19 changes: 18 additions & 1 deletion typings/index.d.ts
Expand Up @@ -795,6 +795,7 @@ declare module 'discord.js' {
public approximatePresenceCount: number | null;
public available: boolean;
public bans: GuildBanManager;
public invites: GuildInviteManager;
public channels: GuildChannelManager;
public commands: GuildApplicationCommandManager;
public defaultMessageNotifications: DefaultMessageNotificationLevel | number;
Expand Down Expand Up @@ -843,7 +844,6 @@ declare module 'discord.js' {
public equals(guild: Guild): boolean;
public fetchAuditLogs(options?: GuildAuditLogsFetchOptions): Promise<GuildAuditLogs>;
public fetchIntegrations(): Promise<Collection<string, Integration>>;
public fetchInvites(): Promise<Collection<string, Invite>>;
public fetchOwner(options?: FetchOwnerOptions): Promise<GuildMember>;
public fetchPreview(): Promise<GuildPreview>;
public fetchTemplates(): Promise<Collection<GuildTemplate['code'], GuildTemplate>>;
Expand Down Expand Up @@ -2340,6 +2340,15 @@ declare module 'discord.js' {
public remove(user: UserResolvable, reason?: string): Promise<User>;
}

export class GuildInviteManager extends BaseManager<Snowflake, Invite, InviteResolvable> {
constructor(guild: Guild, iterable?: Iterable<any>);
public guild: Guild;
public create(channel: GuildChannelResolvable, options?: CreateInviteOptions): Promise<Invite>;
public fetch(options: InviteResolvable | FetchInviteOptions): Promise<Invite>;
public fetch(options?: FetchInvitesOptions): Promise<Collection<string, Invite>>;
public delete(invite: InviteResolvable, reason?: string): Promise<Invite>;
}

export class GuildMemberRoleManager {
constructor(member: GuildMember);
public readonly cache: Collection<Snowflake, Role>;
Expand Down Expand Up @@ -3015,6 +3024,14 @@ declare module 'discord.js' {
cache: boolean;
}

interface FetchInviteOptions extends BaseFetchOptions {
code: string;
}

interface FetchInvitesOptions {
cache: boolean;
}

interface FetchGuildOptions extends BaseFetchOptions {
guild: GuildResolvable;
}
Expand Down