Skip to content

Commit

Permalink
feat: resolve interactions (#503)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeroen Claassens <support@favware.tech>
  • Loading branch information
samfundev and favna committed Aug 14, 2022
1 parent 8ca7d7e commit a09b4c2
Show file tree
Hide file tree
Showing 16 changed files with 248 additions and 133 deletions.
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@discordjs/builders": "^0.16.0",
"@sapphire/discord-utilities": "^2.11.5",
"@sapphire/discord.js-utilities": "^4.11.3",
"@sapphire/discord.js-utilities": "^4.12.0",
"@sapphire/lexure": "^1.0.1",
"@sapphire/pieces": "^3.5.0",
"@sapphire/ratelimits": "^2.4.4",
Expand All @@ -51,12 +51,12 @@
"@sapphire/prettier-config": "^1.4.3",
"@sapphire/ts-config": "^3.3.4",
"@types/jest": "^28.1.6",
"@types/node": "^18.6.4",
"@types/node": "^18.7.3",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"cz-conventional-changelog": "^3.3.0",
"discord.js": "^13.9.2",
"discord.js": "^13.10.2",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
Expand All @@ -67,7 +67,7 @@
"pinst": "^3.0.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"rollup": "^2.77.2",
"rollup": "^2.77.3",
"rollup-plugin-version-injector": "^1.3.3",
"ts-jest": "^28.0.7",
"typedoc": "^0.23.10",
Expand Down
12 changes: 10 additions & 2 deletions src/arguments/CoreMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ export class CoreArgument extends Argument<Message> {
super(context, { name: 'message' });
}

public async run(parameter: string, context: Omit<MessageResolverOptions, 'message'> & Argument.Context): Argument.AsyncResult<Message> {
public async run(
parameter: string,
context: Omit<MessageResolverOptions, 'messageOrInteraction'> & Argument.Context
): Argument.AsyncResult<Message> {
const channel = context.channel ?? context.message.channel;
const resolved = await resolveMessage(parameter, { message: context.message, channel: context.channel, scan: context.scan ?? false });
const resolved = await resolveMessage(parameter, {
messageOrInteraction: context.message,
channel: context.channel,
scan: context.scan ?? false
});

return resolved.mapErrInto((identifier) =>
this.error({
parameter,
Expand Down
10 changes: 8 additions & 2 deletions src/lib/resolvers/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ export function resolveBoolean(
customs?: { truths?: readonly string[]; falses?: readonly string[] }
): Result<boolean, Identifiers.ArgumentBooleanError> {
const boolean = parameter.toLowerCase();
if ([...baseTruths, ...(customs?.truths ?? [])].includes(boolean)) return Result.ok(true);
if ([...baseFalses, ...(customs?.falses ?? [])].includes(boolean)) return Result.ok(false);

if ([...baseTruths, ...(customs?.truths ?? [])].includes(boolean)) {
return Result.ok(true);
}

if ([...baseFalses, ...(customs?.falses ?? [])].includes(boolean)) {
return Result.ok(false);
}

return Result.err(Identifiers.ArgumentBooleanError);
}
15 changes: 11 additions & 4 deletions src/lib/resolvers/channel.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { ChannelMentionRegex, ChannelTypes } from '@sapphire/discord.js-utilities';
import { container } from '@sapphire/pieces';
import { Result } from '@sapphire/result';
import type { Message, Snowflake } from 'discord.js';
import type { BaseCommandInteraction, Message, Snowflake } from 'discord.js';
import { Identifiers } from '../errors/Identifiers';

export function resolveChannel(parameter: string, message: Message): Result<ChannelTypes, Identifiers.ArgumentChannelError> {
export function resolveChannel(
parameter: string,
messageOrInteraction: Message | BaseCommandInteraction
): Result<ChannelTypes, Identifiers.ArgumentChannelError> {
const channelId = (ChannelMentionRegex.exec(parameter)?.[1] ?? parameter) as Snowflake;
const channel = (message.guild ? message.guild.channels : container.client.channels).cache.get(channelId);
if (channel) return Result.ok(channel as ChannelTypes);
const channel = (messageOrInteraction.guild ? messageOrInteraction.guild.channels : container.client.channels).cache.get(channelId);

if (channel) {
return Result.ok(channel as ChannelTypes);
}

return Result.err(Identifiers.ArgumentChannelError);
}
14 changes: 11 additions & 3 deletions src/lib/resolvers/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ export function resolveDate(
const parsed = new Date(parameter);

const time = parsed.getTime();
if (Number.isNaN(time)) return Result.err(Identifiers.ArgumentDateError);

if (typeof options?.minimum === 'number' && time < options.minimum) return Result.err(Identifiers.ArgumentDateTooEarly);
if (typeof options?.maximum === 'number' && time > options.maximum) return Result.err(Identifiers.ArgumentDateTooFar);
if (Number.isNaN(time)) {
return Result.err(Identifiers.ArgumentDateError);
}

if (typeof options?.minimum === 'number' && time < options.minimum) {
return Result.err(Identifiers.ArgumentDateTooEarly);
}

if (typeof options?.maximum === 'number' && time > options.maximum) {
return Result.err(Identifiers.ArgumentDateTooFar);
}

return Result.ok(parsed);
}
11 changes: 7 additions & 4 deletions src/lib/resolvers/dmChannel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { isDMChannel } from '@sapphire/discord.js-utilities';
import { Result } from '@sapphire/result';
import type { DMChannel, Message } from 'discord.js';
import type { BaseCommandInteraction, DMChannel, Message } from 'discord.js';
import { Identifiers } from '../errors/Identifiers';
import { resolveChannel } from './channel';

export function resolveDMChannel(
parameter: string,
message: Message
messageOrInteraction: Message | BaseCommandInteraction
): Result<DMChannel, Identifiers.ArgumentChannelError | Identifiers.ArgumentDMChannelError> {
const result = resolveChannel(parameter, message);
const result = resolveChannel(parameter, messageOrInteraction);
return result.mapInto((value) => {
if (isDMChannel(value) && !value.partial) return Result.ok(value);
if (isDMChannel(value) && !value.partial) {
return Result.ok(value);
}

return Result.err<Identifiers.ArgumentDMChannelError>(Identifiers.ArgumentDMChannelError);
});
}
14 changes: 11 additions & 3 deletions src/lib/resolvers/float.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ export function resolveFloat(
options?: { minimum?: number; maximum?: number }
): Result<number, Identifiers.ArgumentFloatError | Identifiers.ArgumentFloatTooSmall | Identifiers.ArgumentFloatTooLarge> {
const parsed = Number(parameter);
if (Number.isNaN(parsed)) return Result.err(Identifiers.ArgumentFloatError);

if (typeof options?.minimum === 'number' && parsed < options.minimum) return Result.err(Identifiers.ArgumentFloatTooSmall);
if (typeof options?.maximum === 'number' && parsed > options.maximum) return Result.err(Identifiers.ArgumentFloatTooLarge);
if (Number.isNaN(parsed)) {
return Result.err(Identifiers.ArgumentFloatError);
}

if (typeof options?.minimum === 'number' && parsed < options.minimum) {
return Result.err(Identifiers.ArgumentFloatTooSmall);
}

if (typeof options?.maximum === 'number' && parsed > options.maximum) {
return Result.err(Identifiers.ArgumentFloatTooLarge);
}

return Result.ok(parsed);
}
6 changes: 5 additions & 1 deletion src/lib/resolvers/guildChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Identifiers } from '../errors/Identifiers';

export function resolveGuildChannel(parameter: string, guild: Guild): Result<GuildBasedChannelTypes, Identifiers.ArgumentGuildChannelError> {
const channel = resolveById(parameter, guild) ?? resolveByQuery(parameter, guild);
if (channel) return Result.ok(channel);

if (channel) {
return Result.ok(channel);
}

return Result.err(Identifiers.ArgumentGuildChannelError);
}

Expand Down
14 changes: 11 additions & 3 deletions src/lib/resolvers/integer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ export function resolveInteger(
options?: { minimum?: number; maximum?: number }
): Result<number, Identifiers.ArgumentIntegerError | Identifiers.ArgumentIntegerTooSmall | Identifiers.ArgumentIntegerTooLarge> {
const parsed = Number(parameter);
if (!Number.isInteger(parsed)) return Result.err(Identifiers.ArgumentIntegerError);

if (typeof options?.minimum === 'number' && parsed < options.minimum) return Result.err(Identifiers.ArgumentIntegerTooSmall);
if (typeof options?.maximum === 'number' && parsed > options.maximum) return Result.err(Identifiers.ArgumentIntegerTooLarge);
if (!Number.isInteger(parsed)) {
return Result.err(Identifiers.ArgumentIntegerError);
}

if (typeof options?.minimum === 'number' && parsed < options.minimum) {
return Result.err(Identifiers.ArgumentIntegerTooSmall);
}

if (typeof options?.maximum === 'number' && parsed > options.maximum) {
return Result.err(Identifiers.ArgumentIntegerTooLarge);
}

return Result.ok(parsed);
}
6 changes: 5 additions & 1 deletion src/lib/resolvers/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { Identifiers } from '../errors/Identifiers';

export async function resolveMember(parameter: string, guild: Guild): Promise<Result<GuildMember, Identifiers.ArgumentMemberError>> {
const member = (await resolveById(parameter, guild)) ?? (await resolveByQuery(parameter, guild));
if (member) return Result.ok(member);

if (member) {
return Result.ok(member);
}

return Result.err(Identifiers.ArgumentMemberError);
}

Expand Down
87 changes: 64 additions & 23 deletions src/lib/resolvers/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ChannelMessageRegex, MessageLinkRegex, SnowflakeRegex } from '@sapphire/discord-utilities';
import {
AnyInteraction,
GuildBasedChannelTypes,
isAnyInteraction,
isGuildBasedChannel,
isNewsChannel,
isTextBasedChannel,
Expand All @@ -23,9 +25,9 @@ export interface MessageResolverOptions {
*/
channel?: TextBasedChannelTypes;
/**
* Base message to resolve the message from (e.g. pick the channel if not given).
* Base {@link Message} or {@link AnyInteraction} to resolve the message from (e.g. pick the channel if not given).
*/
message: Message;
messageOrInteraction: Message | AnyInteraction;
/**
* Whether to scan the entire guild cache for the message.
* If channel is given with this option, this option is ignored.
Expand All @@ -37,55 +39,94 @@ export interface MessageResolverOptions {
export async function resolveMessage(parameter: string, options: MessageResolverOptions): Promise<Result<Message, Identifiers.ArgumentMessageError>> {
const message =
(await resolveById(parameter, options)) ??
(await resolveByLink(parameter, options.message)) ??
(await resolveByChannelAndMessage(parameter, options.message));
if (message) return Result.ok(message);
(await resolveByLink(parameter, options)) ??
(await resolveByChannelAndMessage(parameter, options));

if (message) {
return Result.ok(message);
}

return Result.err(Identifiers.ArgumentMessageError);
}

function resolveById(parameter: string, options: MessageResolverOptions): Awaitable<Message | null> {
if (!SnowflakeRegex.test(parameter)) return null;
if (!SnowflakeRegex.test(parameter)) {
return null;
}

if (options.channel) return options.channel.messages.fetch(parameter as Snowflake);
if (options.channel) {
return options.channel.messages.fetch(parameter as Snowflake);
}

if (options.scan && isGuildBasedChannel(options.message.channel)) {
for (const channel of options.message.channel.guild.channels.cache.values()) {
if (options.scan && isGuildBasedChannel(options.messageOrInteraction.channel)) {
for (const channel of options.messageOrInteraction.channel.guild.channels.cache.values()) {
if (!isTextBasedChannel(channel)) continue;

const message = channel.messages.cache.get(parameter);
if (message) return message;
if (message) {
return message;
}
}
}

return options.message.channel.messages.fetch(parameter as Snowflake);
return options.messageOrInteraction.channel?.messages.fetch(parameter as Snowflake) ?? null;
}

async function resolveByLink(parameter: string, message: Message): Promise<Message | null> {
if (!message.guild) return null;
async function resolveByLink(parameter: string, options: MessageResolverOptions): Promise<Message | null> {
if (!options.messageOrInteraction.guild) {
return null;
}

const matches = MessageLinkRegex.exec(parameter);
if (!matches) return null;
if (!matches) {
return null;
}

const [, guildId, channelId, messageId] = matches;

const guild = container.client.guilds.cache.get(guildId as Snowflake);
if (guild !== message.guild) return null;
if (guild !== options.messageOrInteraction.guild) {
return null;
}

return getMessageFromChannel(channelId, messageId, message.author);
return getMessageFromChannel(
channelId,
messageId,
isAnyInteraction(options.messageOrInteraction) ? options.messageOrInteraction.user : options.messageOrInteraction.author
);
}

async function resolveByChannelAndMessage(parameter: string, message: Message): Promise<Message | null> {
async function resolveByChannelAndMessage(parameter: string, options: MessageResolverOptions): Promise<Message | null> {
const result = ChannelMessageRegex.exec(parameter)?.groups;
if (!result) return null;

return getMessageFromChannel(result.channelId, result.messageId, message.author);
if (!result) {
return null;
}

return getMessageFromChannel(
result.channelId,
result.messageId,
isAnyInteraction(options.messageOrInteraction) ? options.messageOrInteraction.user : options.messageOrInteraction.author
);
}

async function getMessageFromChannel(channelId: Snowflake, messageId: Snowflake, originalAuthor: User): Promise<Message | null> {
const channel = container.client.channels.cache.get(channelId) as GuildBasedChannelTypes;
if (!channel) return null;
if (!(isNewsChannel(channel) || isTextChannel(channel))) return null;
if (!channel.viewable) return null;
if (!channel.permissionsFor(originalAuthor)?.has(Permissions.FLAGS.VIEW_CHANNEL)) return null;
if (!channel) {
return null;
}

if (!(isNewsChannel(channel) || isTextChannel(channel))) {
return null;
}

if (!channel.viewable) {
return null;
}

if (!channel.permissionsFor(originalAuthor)?.has(Permissions.FLAGS.VIEW_CHANNEL)) {
return null;
}

return channel.messages.fetch(messageId);
}
13 changes: 10 additions & 3 deletions src/lib/resolvers/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ export function resolveNumber(
options?: { minimum?: number; maximum?: number }
): Result<number, Identifiers.ArgumentNumberError | Identifiers.ArgumentNumberTooSmall | Identifiers.ArgumentNumberTooLarge> {
const parsed = Number(parameter);
if (Number.isNaN(parsed)) return Result.err(Identifiers.ArgumentNumberError);
if (Number.isNaN(parsed)) {
return Result.err(Identifiers.ArgumentNumberError);
}

if (typeof options?.minimum === 'number' && parsed < options.minimum) return Result.err(Identifiers.ArgumentNumberTooSmall);
if (typeof options?.maximum === 'number' && parsed > options.maximum) return Result.err(Identifiers.ArgumentNumberTooLarge);
if (typeof options?.minimum === 'number' && parsed < options.minimum) {
return Result.err(Identifiers.ArgumentNumberTooSmall);
}

if (typeof options?.maximum === 'number' && parsed > options.maximum) {
return Result.err(Identifiers.ArgumentNumberTooLarge);
}

return Result.ok(parsed);
}
5 changes: 4 additions & 1 deletion src/lib/resolvers/partialDMChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export function resolvePartialDMChannel(
): Result<DMChannel | PartialDMChannel, Identifiers.ArgumentChannelError | Identifiers.ArgumentDMChannelError> {
const result = resolveChannel(parameter, message);
return result.mapInto((channel) => {
if (isDMChannel(channel)) return Result.ok(channel);
if (isDMChannel(channel)) {
return Result.ok(channel);
}

return Result.err<Identifiers.ArgumentDMChannelError>(Identifiers.ArgumentDMChannelError);
});
}
6 changes: 5 additions & 1 deletion src/lib/resolvers/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { Identifiers } from '../errors/Identifiers';

export async function resolveRole(parameter: string, guild: Guild): Promise<Result<Role, Identifiers.ArgumentRoleError>> {
const role = (await resolveById(parameter, guild)) ?? resolveByQuery(parameter, guild);
if (role) return Result.ok(role);

if (role) {
return Result.ok(role);
}

return Result.err(Identifiers.ArgumentRoleError);
}

Expand Down

0 comments on commit a09b4c2

Please sign in to comment.