Skip to content

Commit

Permalink
feat: add interactions suport (#51)
Browse files Browse the repository at this point in the history
* feat: add support for interactions

* test: add more tests

* feat: add option resolver

* docs: fix links

* test: use dapi-type

* test: add some temporary tests

* fix: add sub-command-group prefix

* fix: mentionable test

* test: add some temporary tests to make sure everything is working

Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
  • Loading branch information
UndiedGamer and imranbarbhuiya committed May 28, 2022
1 parent 6ed4692 commit daea9db
Show file tree
Hide file tree
Showing 18 changed files with 400 additions and 110 deletions.
5 changes: 3 additions & 2 deletions packages/tagscript-plugin-discord/src/lib/Transformer/Base.ts
@@ -1,5 +1,6 @@
import type { Lexer, ITransformer } from 'tagscript';
import type { GuildTextBasedChannel, Role, User, GuildMember, Guild } from 'discord.js';
import type { Role, User, GuildMember, Guild, CommandInteraction } from 'discord.js';
import { GuildChannel } from '../interfaces';

export type outputResolvable = string | number | boolean | null | undefined;

Expand All @@ -10,7 +11,7 @@ export interface SafeValues<T> {
/**
* Transformer for {@link https://discord.js.org/#/ Discord.js} objects.
*/
export abstract class BaseTransformer<T extends GuildTextBasedChannel | Role | User | GuildMember | Guild> implements ITransformer {
export abstract class BaseTransformer<T extends GuildChannel | Role | User | GuildMember | Guild | CommandInteraction> implements ITransformer {
protected base: T;
protected safeValues: SafeValues<T> = {};

Expand Down
@@ -1,8 +1,8 @@
import type { GuildTextBasedChannel } from 'discord.js';
import { GuildChannel } from '../interfaces';
import { BaseTransformer } from './Base';

/**
* Transformer for Discord {@link https://discord.js.org/#/docs/discord.js/stable/class/BaseGuildTextChannel GuildTextBasedChannel}
* Transformer for Discord {@link GuildChannel}
*
* @properties
* ```yaml
Expand All @@ -21,12 +21,12 @@ import { BaseTransformer } from './Base';
* createdTimestamp: Gives channel create date in ms.
* slowmode: Gives channel slowmode.
*/
export class ChannelTransformer extends BaseTransformer<GuildTextBasedChannel> {
export class ChannelTransformer extends BaseTransformer<GuildChannel> {
protected override updateSafeValues() {
this.safeValues.topic = 'topic' in this.base ? this.base.topic : '';
this.safeValues.type = this.base.type;
this.safeValues.position = 'position' in this.base ? this.base.position : 0;
this.safeValues.nsfw = 'nsfw' in this.base ? this.base.nsfw : this.base.parent?.nsfw ?? false;
this.safeValues.nsfw = 'nsfw' in this.base ? this.base.nsfw : this.base.parent && 'nsfw' in this.base.parent ? this.base.parent.nsfw : false;
this.safeValues.parentId = this.base.parentId;
this.safeValues.parentName = this.base.parent?.name ?? '';
this.safeValues.parentType = this.base.parent?.type ?? '';
Expand Down
@@ -0,0 +1,14 @@
import { CommandInteraction } from 'discord.js';
import { BaseTransformer } from './Base';

export class InteractionTransformer extends BaseTransformer<CommandInteraction> {
protected override updateSafeValues() {
this.safeValues.applicationId = this.base.applicationId;
this.safeValues.channelId = this.base.channelId;
this.safeValues.guildId = this.base.guildId;
this.safeValues.commandId = this.base.commandId;
this.safeValues.commandName = this.base.commandName;
this.safeValues.locale = this.base.locale;
this.safeValues.guildLocale = this.base.guildLocale;
}
}
Expand Up @@ -5,3 +5,4 @@ export * from './GuildMember';
export * from './GuildTextBasedChannel';
export * from './Role';
export * from './User';
export * from './Interaction';
@@ -0,0 +1,84 @@
import { Channel, CommandInteractionOption, CommandInteractionOptionResolver, GuildMember, Role, User } from 'discord.js';
import { IntegerTransformer, ITransformer, StringTransformer } from 'tagscript';
import { ChannelTransformer, MemberTransformer, RoleTransformer, UserTransformer } from '../Transformer';

export const mapOptions = (options: readonly CommandInteractionOption[], transformers: Record<string, ITransformer>, prefix = '') => {
options.forEach((data) => {
switch (data.type) {
case 'SUB_COMMAND_GROUP':
transformers.subCommandGroup = new StringTransformer(data.value as string);
mapOptions(data.options!, transformers, `${data.name}-`);
break;
case 'SUB_COMMAND':
transformers.subCommand = new StringTransformer(data.value as string);
mapOptions(data.options!, transformers, `${prefix}${data.name}-`);
break;
case 'STRING':
transformers[prefix + data.name] = new StringTransformer(data.value as string);
break;
case 'BOOLEAN':
transformers[prefix + data.name] = new StringTransformer(data.value as string);
break;
case 'INTEGER':
transformers[prefix + data.name] = new IntegerTransformer(data.value as `${number}`);
break;
case 'NUMBER':
transformers[prefix + data.name] = new IntegerTransformer(data.value as `${number}`);
break;
case 'MENTIONABLE':
transformers[prefix + data.name] =
data.member instanceof GuildMember
? new MemberTransformer(data.member)
: data.role instanceof Role
? new RoleTransformer(data.role)
: data.user instanceof User
? new UserTransformer(data.user)
: // added only for test. Will be removed after rewriting these tests
new StringTransformer(data.value as string);
break;
case 'USER':
transformers[prefix + data.name] =
data.member instanceof GuildMember
? new MemberTransformer(data.member)
: data.user
? new UserTransformer(data.user)
: // added only for test. Will be removed after rewriting these tests
new StringTransformer(data.value as string);
break;
case 'ROLE':
data.role instanceof Role && (transformers[prefix + data.name] = new RoleTransformer(data.role));
break;
case 'CHANNEL':
data.channel instanceof Channel && (transformers[prefix + data.name] = new ChannelTransformer(data.channel));
break;
case 'ATTACHMENT':
transformers[prefix + data.name] = new StringTransformer(data.attachment!.url);
}
});
};

/**
*
* Resolves {@link https://discord.js.org/#/docs/discord.js/stable/class/CommandInteractionOptionResolver CommandInteractionOptionResolver} options into {@link Record<string, ITransformer>}.
*
* @usage
* ```typescript
* client.on('interactionCreate', async interaction => {
* if (!interaction.isCommand()) return;
*
* if (interaction.commandName === 'ping') {
* const result = await ts.run(str, resolveCommandOptions(interaction.options));
* await interaction.reply(result.body);
* }
});
* ```
*/
export const resolveCommandOptions = (options: Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>) => {
const optionData = options.data;

const transformers: Record<string, ITransformer> = {};

mapOptions(optionData, transformers);

return transformers;
};
1 change: 1 addition & 0 deletions packages/tagscript-plugin-discord/src/lib/Utils/index.ts
@@ -0,0 +1 @@
export * from './CommandInteraction';
1 change: 1 addition & 0 deletions packages/tagscript-plugin-discord/src/lib/index.ts
@@ -1,3 +1,4 @@
export * from './interfaces';
export * from './Transformer';
export * from './Parsers';
export * from './Utils';
@@ -1,4 +1,4 @@
import { MessageEmbedOptions } from 'discord.js';
import { AnyChannel, Guild, MessageEmbedOptions } from 'discord.js';
import 'tagscript';

declare module 'tagscript' {
Expand All @@ -13,3 +13,5 @@ declare module 'tagscript' {
files?: string[];
}
}

export type GuildChannel = Extract<AnyChannel, { guild: Guild }>;

0 comments on commit daea9db

Please sign in to comment.