Skip to content
This repository has been archived by the owner on Jan 8, 2022. It is now read-only.

feat:@discord.js/type-utils package #57

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/type-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
3 changes: 3 additions & 0 deletions packages/type-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `@discordjs/type-utils`

> The type utilities module for Discord.js
21 changes: 21 additions & 0 deletions packages/type-utils/__tests__/button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MessageButtonOptions } from 'discord.js';
import { isLinkButtonOptions } from '../src/lib/button';

test('isLinkButtonOptions', () => {
const buttonData: MessageButtonOptions = {
style: 'LINK',
url: 'test',
};

expect(isLinkButtonOptions(buttonData)).toBe(true);

const invalidData: MessageButtonOptions = {
style: 'PRIMARY',
customId: '1234',
};

expect(isLinkButtonOptions(invalidData)).toBe(false);

// @ts-ignore
expect(() => isLinkButtonOptions({})).toThrowError();
});
80 changes: 80 additions & 0 deletions packages/type-utils/__tests__/command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ApplicationCommandData, ApplicationCommandOptionData } from 'discord.js';
import {
isChatInputCommandData,
isContextMenuCommandData,
optionDataSupportsChoices,
optionDataSupportsSubOptions,
} from '../src/lib/command';
import { validateType } from '../src/lib/util/enum';

test('validateType', () => {
expect(() => validateType({})).toThrowError();
});

test('isContextMenuCommand', () => {
const nonTypedData: ApplicationCommandData = {
name: 'test',
description: 'test',
};

expect(isContextMenuCommandData(nonTypedData)).toBe(false);
expect(isChatInputCommandData(nonTypedData)).toBe(true);

const typedData: ApplicationCommandData = {
...nonTypedData,
type: 'CHAT_INPUT',
};

expect(isContextMenuCommandData(typedData)).toBe(false);

const invalidMessageData: ApplicationCommandData = {
name: 'test',
type: 'MESSAGE',
};

expect(isContextMenuCommandData(invalidMessageData)).toBe(true);
expect(isChatInputCommandData(invalidMessageData)).toBe(false);

const invalidUserData: ApplicationCommandData = {
name: 'test',
type: 'USER',
};

expect(isContextMenuCommandData(invalidUserData)).toBe(true);
});

test('optionsDataSupportsChoices', () => {
const subCommandData: ApplicationCommandOptionData = {
name: 'test',
type: 'SUB_COMMAND',
description: 'test',
};

expect(optionDataSupportsChoices(subCommandData)).toBe(false);

const stringData: ApplicationCommandOptionData = {
name: 'test',
type: 'STRING',
description: 'test',
};

expect(optionDataSupportsChoices(stringData)).toBe(true);
});

test('optionsDataSupportsSubOptions', () => {
const subCommandData: ApplicationCommandOptionData = {
name: 'test',
type: 'SUB_COMMAND',
description: 'test',
};

expect(optionDataSupportsSubOptions(subCommandData)).toBe(true);

const stringData: ApplicationCommandOptionData = {
name: 'test',
type: 'STRING',
description: 'test',
};

expect(optionDataSupportsSubOptions(stringData)).toBe(false);
});
48 changes: 48 additions & 0 deletions packages/type-utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "http://json.schemastore.org/package",
"name": "@discordjs/type-utils",
"version": "0.1.0-canary.0",
"description": "Type utilities for Discord.js and discord api types.",
"contributors": [
"Suneet Tipirneni <suneettipirneni@icloud.com>",
"NotSugden <email>"
],
"license": "Apache-2.0",
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc --build --force"
},
"repository": {
"type": "git",
"url": "git+https://github.com/discordjs/discord.js-modules.git"
},
"bugs": {
"url": "https://github.com/discordjs/discord.js-modules/issues"
},
"homepage": "https://github.com/discordjs/discord.js-modules/tree/main/packages/type-utils",
"keywords": [
"discord",
"typeguards",
"typescript",
"discordapp",
"discordjs"
],
"main": "dist/index.js",
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"dist"
],
"devDependencies": {
"@types/node-fetch": "^2.5.10",
"discord.js": "^13.1.0"
},
"engines": {
"node": ">=16.0.0"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions packages/type-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/command';
17 changes: 17 additions & 0 deletions packages/type-utils/src/lib/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MessageButtonOptions, Constants, LinkButtonOptions } from 'discord.js';
import { isPartOfEnum } from './util/enum';

const { MessageButtonStyles } = Constants;

/**
* Verifies if the given message button options support URLs or not.
* @param messageButtonOptions The message button options to check.
* @returns True if the option supports URLs, false otherwise.
*/
export function isLinkButtonOptions(buttonData: MessageButtonOptions): buttonData is LinkButtonOptions {
if (!('style' in buttonData)) {
throw new TypeError('INVALID_TYPE');
}

return isPartOfEnum(buttonData.style, MessageButtonStyles, ['LINK']);
}
66 changes: 66 additions & 0 deletions packages/type-utils/src/lib/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
ApplicationCommandData,
UserApplicationCommandData,
MessageApplicationCommandData,
ApplicationCommandChoicesData,
ApplicationCommandOptionData,
ApplicationCommandSubCommandData,
ApplicationCommandSubGroupData,
ChatInputApplicationCommandData,
Constants,
} from 'discord.js';
import { isPartOfEnum, validateType } from './util/enum';

const { ApplicationCommandOptionTypes, ApplicationCommandTypes } = Constants;

/**
* Whether or not the command supports a description or options.
* @param commandData The application command data to check.
* @returns True if the command is a context menu command, false otherwise.
*/
export function isContextMenuCommandData(
commandData: ApplicationCommandData,
): commandData is UserApplicationCommandData | MessageApplicationCommandData {
if (!commandData.type) {
return false;
}

validateType(commandData);

return isPartOfEnum(commandData.type, ApplicationCommandTypes, ['MESSAGE', 'USER']);
}

/**
* Whether or not the command supports a description or options.
* @param commandData The application command data to check.
* @returns True if the command is a chat input command, false otherwise.
*/
export function isChatInputCommandData(
commandData: ApplicationCommandData,
): commandData is ChatInputApplicationCommandData {
return !isContextMenuCommandData(commandData);
}

/**
* Verifies if the given command option data supports choices or not.
* @param commandOptionData The command option data to check.
* @returns True if the option supports choices, false otherwise.
*/
export function optionDataSupportsChoices(
optionData: ApplicationCommandOptionData,
): optionData is ApplicationCommandChoicesData {
validateType(optionData);
return isPartOfEnum(optionData.type, ApplicationCommandOptionTypes, ['INTEGER', 'STRING', 'NUMBER']);
}

/**
* Verifies if the given command option data supports choices or not.
* @param {ApplicationCommandOptionData} commandOptionData The command option data to check.
* @returns {boolean} True if the option supports choices, false otherwise.
*/
export function optionDataSupportsSubOptions(
optionData: ApplicationCommandOptionData,
): optionData is ApplicationCommandSubGroupData | ApplicationCommandSubCommandData {
validateType(optionData);
return isPartOfEnum(optionData.type, ApplicationCommandOptionTypes, ['SUB_COMMAND', 'SUB_COMMAND_GROUP']);
}
23 changes: 23 additions & 0 deletions packages/type-utils/src/lib/util/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Resolves a given type to an enum equivalent value, and
* checks if it's part of the given enum type.
* @param type The type to resolve
* @param object The enum to resolve to
* @param fields The enum fields to check
* @returns Whether the type is part of the enum or not.
*/
export function isPartOfEnum(type: string | number, object: Record<string, unknown>, fields: string[]): boolean {
const resolvedType = typeof type === 'number' ? type : object[type];
return fields.some((field) => object[field] === resolvedType);
}

/**
* Throws a type error if the object has no `type field`.
* @param object The object to type check.
* @private
*/
export function validateType(object: any) {
if (!('type' in object)) {
throw new TypeError('INVALID_TYPE');
}
}
11 changes: 11 additions & 0 deletions packages/type-utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"sourceRoot": "./",
"rootDir": "./src",
"outDir": "dist",
"lib": ["ESNext"]
},
"include": ["src"],
"references": [{ "path": "../core" }]
}
Loading