Skip to content

Commit

Permalink
Merge pull request #14 from DeathVenom54/dev
Browse files Browse the repository at this point in the history
v2.0.0
  • Loading branch information
0xDevansh authored Sep 10, 2022
2 parents 7102e4e + 0c40863 commit e46574b
Show file tree
Hide file tree
Showing 22 changed files with 258 additions and 526 deletions.
2 changes: 1 addition & 1 deletion docs/assets/search.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.html

Large diffs are not rendered by default.

20 changes: 7 additions & 13 deletions docs/interfaces/MarshalOptions.html

Large diffs are not rendered by default.

65 changes: 22 additions & 43 deletions docs/modules.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "djs-marshal",
"description": "A simple slash command manager for typescript",
"version": "1.3.2",
"version": "2.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": "https://github.com/DeathVenom54/djs-marshal.git",
Expand All @@ -22,14 +22,14 @@
"docs": "typedoc --plugin ./node_modules/typedoc-theme-hierarchy/dist/index.js"
},
"peerDependencies": {
"discord.js": ">=13.x.x"
"discord.js": ">=14.x.x"
},
"devDependencies": {
"@types/chalk": "^2.2.0",
"@types/deep-equal": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"discord.js": "^13.6.0",
"discord.js": "14",
"eslint": "^8.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
Expand Down
11 changes: 6 additions & 5 deletions src/core/buttons/loadButtons.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ButtonCommand } from '../../structures/ButtonCommand';
import { Client, Collection } from 'discord.js';
import { logVerbose, logWarning } from '../../utils/logger';

/**
* Loads the buttons provided into client.buttons
Expand All @@ -9,21 +8,23 @@ import { logVerbose, logWarning } from '../../utils/logger';
* @param {ButtonCommand[]} buttons The buttons to load
*/
export const loadButtons = (client: Client, buttons: ButtonCommand[]): void => {
logVerbose('Loading buttons', client);
client.logMethod('Loading buttons', 'verbose');
const buttonCollection = new Collection<string | RegExp, ButtonCommand>();

buttons.forEach((button) => {
// checks
if (button.beforeExecute?.deferReplyEphemeral && button.beforeExecute?.deferReply) {
logWarning(`deferReply and deferReplyEphemeral are both set to true for button ${button.customId}`, client);
client.logMethod(
`deferReply and deferReplyEphemeral are both set to true for button ${button.customId}`,
'erroronly',
);
}
const defer = button.beforeExecute?.deferReplyEphemeral || button.beforeExecute?.deferReply;
if (defer && button.beforeExecute?.deferUpdate) {
logWarning(`deferReply and deferUpdate are both set to true for button ${button.customId}`, client);
client.logMethod(`deferReply and deferUpdate are both set to true for button ${button.customId}`, 'erroronly');
}

buttonCollection.set(button.customId, button);
logVerbose(` ✅ ${button.customId}`, client);
});

client.buttons = buttonCollection;
Expand Down
19 changes: 6 additions & 13 deletions src/core/commands/loadCommands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SlashCommand } from '../../structures/SlashCommand';
import { Client, Collection, Snowflake } from 'discord.js';
import { Client, Collection, Snowflake, ApplicationCommandType } from 'discord.js';
import { syncCommands } from './syncCommands';
import { logWarning, logVerbose, logError } from '../../utils/logger';

/**
* Loads slash commands and stores them as client.commands, then syncs them with Discord
Expand All @@ -15,18 +14,14 @@ export const loadCommands = async (client: Client, commands: SlashCommand[]): Pr
commandsCollection.set('global', []);
commandsCollection.set('allGuild', []);

logVerbose('Loading commands', client);
client.logMethod('Loading commands', 'verbose');
commands.forEach((command) => {
// preload checks
if (command.beforeExecute?.defer && command.beforeExecute?.deferEphemeral)
logWarning(`defer and deferEphemeral are both true for command ${command.name}`, client);
if ('allowWithPermission' in command && command.allowWithPermission === [])
logWarning(`allowWithPermission is [] for ${command.name}, it will be ignored`, client);
client.logMethod(`defer and deferEphemeral are both true for command ${command.name}`, 'erroronly');

if (!command.type) command.type = 'CHAT_INPUT';
if (!command.defaultPermission) command.defaultPermission = true;
if (!command.type) command.type = ApplicationCommandType.ChatInput;
if (command.handleError === undefined) command.handleError = true;
if ('allowWithPermission' in command) command.defaultPermission = !command.allowWithPermission?.length;

// is guild command
if (command.commandType === 'guild' && command.guildId) {
Expand All @@ -38,11 +33,9 @@ export const loadCommands = async (client: Client, commands: SlashCommand[]): Pr
} else {
commandsCollection.get('global')?.push(command);
}

logVerbose(` ✅ ${command.name}`, client);
});

client.commands = commandsCollection;
logVerbose('Loaded all commands', client);
await syncCommands(client).catch((err) => logError(err, client));
client.logMethod('Loaded all commands', 'verbose');
await syncCommands(client).catch((err) => client.logMethod(err, 'erroronly'));
};
171 changes: 8 additions & 163 deletions src/core/commands/syncCommands.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,16 @@
import {
ApplicationCommand,
ApplicationCommandPermissionData,
Client,
ClientApplication,
Guild,
Intents,
Role,
Snowflake,
} from 'discord.js';
import { Client, ClientApplication, Guild } from 'discord.js';
import { SlashCommand } from '../../structures/SlashCommand';
import deepEqual from 'deep-equal';
import { logVerbose } from '../../utils/logger';
import { toApplicationCommand } from '../../utils/toApplicationCommand';

/**
* Sets the permissions of a registered ApplicationCommand according to SlashCommand properties
*
* @param {ApplicationCommand} registered The registered application command
* @param {SlashCommand} command The registered slash command
*/
const setPermissions = async (registered: ApplicationCommand, command: SlashCommand): Promise<void> => {
const permissions: ApplicationCommandPermissionData[] = (command.allowRoles || []).map((id) => {
return { id, type: 'ROLE', permission: true };
});
permissions.push(
...(command.denyRoles || []).map((id) => {
return <ApplicationCommandPermissionData>{ id, type: 'ROLE', permission: false };
}),
);
permissions.push(
...(command.allowUsers || []).map((id) => {
return <ApplicationCommandPermissionData>{ id, type: 'USER', permission: true };
}),
);
permissions.push(
...(command.denyUsers || []).map((id) => {
return <ApplicationCommandPermissionData>{ id, type: 'USER', permission: false };
}),
);

if (permissions.length) await registered.permissions.set({ permissions });
};

/**
* Compares and syncs global commands if needed
*
* @param application The client's application (client.application)
* @param {SlashCommand[]} newCommands The commands to sync
*/
const syncGlobalCommands = async (application: ClientApplication, newCommands: SlashCommand[]): Promise<void> => {
logVerbose('Syncing global commands', application.client);
application.client.logMethod('Syncing global commands', 'verbose');
const currentCommands = await application.commands.fetch();
const cc = [...currentCommands.values()];

Expand All @@ -59,21 +20,20 @@ const syncGlobalCommands = async (application: ClientApplication, newCommands: S

// command is new
if (!matching) {
logVerbose(`Syncing new global command: ${command.name}`, application.client);
application.client.logMethod(`Syncing new global command: ${command.name}`, 'verbose');
await application.commands.create(command);
logVerbose(` ✅ ${command.name}`, application.client);
continue;
}

// command has changed
if (!deepEqual(matching, toApplicationCommand(command))) {
logVerbose(`Syncing changed global command: ${command.name}`, application.client);
application.client.logMethod(`Syncing changed global command: ${command.name}`, 'verbose');
await application.commands.edit(matching.id, command);
}

// finally, remove from synced commands
cc.splice(cc.indexOf(matching), 1);
logVerbose(` ✅ ${command.name}`, application.client);
application.client.logMethod(` ✅ ${command.name}`, 'verbose');
}

// delete left over commands
Expand All @@ -86,93 +46,6 @@ const syncGlobalCommands = async (application: ClientApplication, newCommands: S
}
};

const syncPermissionForRole = async (
command: SlashCommand,
role: Role,
): Promise<{
allowedRoles: Snowflake[];
allowedMembers: Snowflake[];
}> => {
const allowedRoles: Snowflake[] = [];
const allowedMembers: Snowflake[] = [];
const seenMembers: Snowflake[] = [];

const isAllowed = command.allowWithPermission?.some((perm) => role.permissions.has(perm));
if (isAllowed) allowedRoles.push(role.id);
// doesn't have permission, check members individually
else {
role.members.forEach((member) => {
// make sure member is not repeated
if (seenMembers.includes(member.id)) return;

const memberAllowed = command.allowWithPermission?.some((perm) => member.permissions.has(perm));
if (memberAllowed) allowedMembers.push(member.id);

seenMembers.push(member.id);
});
}

return { allowedRoles, allowedMembers };
};

/**
* Makes sure users with only selected permissions can use the command
*
* @param guild The guild in which command should be checked
* @param command The SlashCommand to be checked
* @param existing The existing ApplicationCommand in guild
*
* @deprecated Buggy and resource heavy, use allowRoles, denyRoles, allowUsers and denyUsers
*/
export const syncPermissions = async (
guild: Guild,
command: SlashCommand,
existing: ApplicationCommand,
): Promise<void> => {
if ('allowWithPermission' in command && command.allowWithPermission?.length) {
// this won't work without GUILDS and GUILD_MEMBERS intents
if (!new Intents(guild.client.options.intents).has('GUILD_MEMBERS'))
throw new Error('allowWithPermission requires the GUILD_MEMBERS intent');

// fetch members for cache
await guild.members.fetch();

const roles = await guild.roles.fetch();
let everyoneRole: Role | undefined = undefined;

const aRoles: Snowflake[] = [];
const aMembers: Snowflake[] = [];

for (const [, role] of roles) {
// ensure that @everyone is checked last
if (role.name === '@everyone') {
everyoneRole = role;
continue;
}

const { allowedMembers, allowedRoles } = await syncPermissionForRole(command, role);
aMembers.push(...allowedMembers);
aRoles.push(...allowedRoles);
}
if (everyoneRole) await syncPermissionForRole(command, everyoneRole);

// add permissions
if (aMembers.length || aRoles.length) {
const commandPerms = aMembers
.map((id) => {
return { type: 'USER', id, permission: true };
})
.concat(
aRoles.map((id) => {
return { type: 'ROLE', id, permission: true };
}),
) as ApplicationCommandPermissionData[];

await existing.permissions.set({ permissions: commandPerms });
}
}
};

/**
* Sync commands of a guild
*
Expand All @@ -182,35 +55,7 @@ export const syncGuildCommands = async (guild: Guild): Promise<void> => {
const commands = guild.client.commands;
const guildCommands = (commands.get('allGuild') || [])?.concat(commands.get(guild.id) || []);

await guild.commands.set([]);

const withoutPerms: SlashCommand[] = [];
const withPerms: SlashCommand[] = [];

guildCommands.forEach((c) => {
if (c.allowRoles?.length || c.denyRoles?.length || c.allowUsers?.length || c.denyUsers?.length) {
withPerms.push(c);
} else {
withoutPerms.push(c);
}
});
await guild.commands.set(withoutPerms);
for (const command of withPerms) {
const registered = await guild.commands.create(command);
await setPermissions(registered, command);
}

// sync permission
await Promise.all(
guildCommands.map(async (com) => {
if ('allowWithPermission' in com && com.allowWithPermission?.length) {
const registered = guild.commands.cache.find((c) => c.name === com.name);

if (!registered) return;
await syncPermissions(guild, com, registered);
}
}),
);
await guild.commands.set(guildCommands);
};

/**
Expand All @@ -230,12 +75,12 @@ export const syncCommands = async (client: Client): Promise<void> => {
if (global) await syncGlobalCommands(application, global);

// sync guild commands
logVerbose(`Syncing guild commands`, client);
application.client.logMethod(`Syncing guild commands`, 'verbose');
const guilds = await client.guilds.fetch();
for (const [, g] of guilds) {
const guild = await g.fetch();
await syncGuildCommands(guild);
}

logVerbose('Successfully synced commands', client);
application.client.logMethod('Successfully synced commands', 'verbose');
};
5 changes: 2 additions & 3 deletions src/core/handlers/handleButtonInteraction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ButtonInteraction } from 'discord.js';
import { logWarning } from '../../utils/logger';

/**
* Intercepts a ButtonInteraction and executes the matching button
Expand All @@ -12,9 +11,9 @@ export const handleButtonInteraction = async (int: ButtonInteraction): Promise<v
});

if (!button)
return logWarning(
return int.client.logMethod(
`Received Button interaction with customId: ${int.customId}, but no such registered button`,
int.client,
'warn',
);

// beforeExecute stuff
Expand Down
36 changes: 0 additions & 36 deletions src/core/handlers/handleGuildMemberUpdate.ts

This file was deleted.

Loading

0 comments on commit e46574b

Please sign in to comment.