This commit is contained in:
2022-03-29 22:31:38 -05:00
commit 9520847cc9
14 changed files with 652 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
lib
examples/*.js

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2022 Ashley Zomo
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

47
README.md Normal file
View File

@@ -0,0 +1,47 @@
# discordslash
Discord Application Commands made easy
## usage
run `npm run buildexample <token>` to compile example(s)
`examples/main.ts`
```ts
import { Client, CommandInteraction, Intents } from 'discord.js';
import { addCommand, CommandGenerator, CommandOptionGenerator, initClient } from '../';
const client = new Client({ intents: [Intents.FLAGS.GUILDS] });
initClient(client);
addCommand([
new CommandGenerator()
.setName('ping')
.setDescription('ping me')
.setRun((interaction: CommandInteraction) =>
interaction.reply('pong')),
new CommandGenerator()
.setName('8ball')
.setDescription('ask the magic 8ball a question')
.addOption([
new CommandOptionGenerator()
.setName('question')
.setType('string')
.setDescription('question to ask the 8ball')
.setRequired()
])
.setRun(EightBall)
]);
client.login(TOKEN);
```
## TODO
- quick option functions
- non global commmands

16
buildexample.js Normal file
View File

@@ -0,0 +1,16 @@
const { build } = require('esbuild');
const define = {
TOKEN: `"${process.argv[2]}"`
};
build({
entryPoints: ['examples/main.ts'],
outfile: 'examples/main.js',
target: 'es6',
platform: 'node',
format: 'cjs',
define
});

1
examples/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare const TOKEN: string;

72
examples/main.ts Normal file
View File

@@ -0,0 +1,72 @@
import { Client, CommandInteraction, Intents } from 'discord.js';
import { addCommand, CommandGenerator, CommandOptionGenerator, initClient } from '../lib/main';
const EightBallAnswers = [
'It is certain',
'It is decidedly so',
'Without a doubt',
'Yes - definitely',
'You may rely on it',
'As I see it, yes',
'Most likely',
'Outlook good',
'Yes',
'Signs point to yes',
'Don\'t count on it',
'My reply is no',
'My sources say no',
'Outlook not so good',
'Very doubtful',
'Reply hazy, try again',
'Ask again later',
'Better not tell you now',
'Cannot predict now',
'Concentrate and ask again'
];
const client = new Client({ intents: [Intents.FLAGS.GUILDS] });
initClient(client);
addCommand([
new CommandGenerator()
.setName('ping')
.setDescription('ping me')
.setRun((interaction: CommandInteraction) =>
interaction.reply('pong')),
new CommandGenerator()
.setName('8ball')
.setDescription('ask the magic 8ball a question')
//.addOption('question', 'string', 'question to ask the 8ball') //will add something like this later
.addOption([
new CommandOptionGenerator()
.setName('question')
.setType('string')
.setDescription('question to ask the 8ball')
.setRequired()
])
.setRun(EightBall)
]);
function EightBall(interaction: CommandInteraction) {
let question = interaction.options.get('question', true).value,
answer = EightBallAnswers[Math.floor(Math.random() * EightBallAnswers.length)];
if (typeof question === 'string') {
if (!question.endsWith('?'))
question += '?';
interaction.reply(`\`${question}\` ${answer}`);
}
}
client.on('ready', client => console.log(`Ready ${client.user.tag}`));
client.login(TOKEN);

7
examples/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true
}
}

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "discordslash",
"description": "",
"version": "1.0.0",
"main": "lib/main.js",
"types": "lib/main.d.ts",
"devDependencies": {},
"scripts": {
"build": "tsc",
"buildexamples": "node buildexample.js",
"_prepare": "npm run build"
},
"repository": {
"type": "git",
"url": "git@git.zomo.dev:zomo/discordslash.git"
},
"author": "ashley zomo",
"license": "ISC",
"dependencies": {
"discord.js": "^13.6.0",
"typescript": "^4.6.3"
}
}

182
pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,182 @@
lockfileVersion: 5.3
specifiers:
discord.js: ^13.6.0
typescript: ^4.6.3
dependencies:
discord.js: 13.6.0
typescript: 4.6.3
packages:
/@discordjs/builders/0.11.0:
resolution: {integrity: sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==}
engines: {node: '>=16.0.0', npm: '>=7.0.0'}
dependencies:
'@sindresorhus/is': 4.6.0
discord-api-types: 0.26.1
ts-mixer: 6.0.1
tslib: 2.3.1
zod: 3.14.3
dev: false
/@discordjs/collection/0.4.0:
resolution: {integrity: sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==}
engines: {node: '>=16.0.0', npm: '>=7.0.0'}
dev: false
/@sapphire/async-queue/1.3.0:
resolution: {integrity: sha512-z+CDw5X4UgIEpZL8KM+ThVx1i8V60HBg0l/oFewTNbQQeRDJHdVxHyJykv+SF1H+Rc8EkMS81VTWo95jVYgO/g==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
/@sindresorhus/is/4.6.0:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
dev: false
/@types/node-fetch/2.6.1:
resolution: {integrity: sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==}
dependencies:
'@types/node': 17.0.23
form-data: 3.0.1
dev: false
/@types/node/17.0.23:
resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==}
dev: false
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
'@types/node': 17.0.23
dev: false
/asynckit/0.4.0:
resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}
dev: false
/combined-stream/1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/delayed-stream/1.0.0:
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
engines: {node: '>=0.4.0'}
dev: false
/discord-api-types/0.26.1:
resolution: {integrity: sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==}
engines: {node: '>=12'}
dev: false
/discord.js/13.6.0:
resolution: {integrity: sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==}
engines: {node: '>=16.6.0', npm: '>=7.0.0'}
dependencies:
'@discordjs/builders': 0.11.0
'@discordjs/collection': 0.4.0
'@sapphire/async-queue': 1.3.0
'@types/node-fetch': 2.6.1
'@types/ws': 8.5.3
discord-api-types: 0.26.1
form-data: 4.0.0
node-fetch: 2.6.7
ws: 8.5.0
transitivePeerDependencies:
- bufferutil
- encoding
- utf-8-validate
dev: false
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/form-data/4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types/2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/tr46/0.0.3:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: false
/ts-mixer/6.0.1:
resolution: {integrity: sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==}
dev: false
/tslib/2.3.1:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
dev: false
/typescript/4.6.3:
resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
/webidl-conversions/3.0.1:
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
dev: false
/whatwg-url/5.0.0:
resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/ws/8.5.0:
resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/zod/3.14.3:
resolution: {integrity: sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==}
dev: false

99
src/discord/main.ts Normal file
View File

@@ -0,0 +1,99 @@
import { MessageEmbed } from 'discord.js';
import { template, resolveColor, bigString } from '../main';
import { embedObject, basicObjectString, authorData, footerData } from '../types';
export * from "../main";
/**
* converts embedObj to Discord.MessageEmbed
*/
export function embedObjEmbed(embedObj: embedObject, args: basicObjectString = {}): MessageEmbed {
const embed = new MessageEmbed(),
{ author, color, description, fields, footer, image, thumbnail, timestamp, title, url } = embedObj;
if (author !== undefined) {
let authorFix: authorData;
if (typeof author === 'string')
authorFix = {
name: template(author, args)
};
else {
const {name, iconURL, url} = author;
authorFix = {
name: template(name, args)
};
if (iconURL !== undefined)
authorFix.iconURL = template(iconURL, args);
if (url !== undefined)
authorFix.url = template(url, args);
}
embed.setAuthor(authorFix);
}
if (footer !== undefined) {
let footerFix: footerData;
if (typeof footer === 'string') {
footerFix = {
text: template(footer, args)
};
} else {
const {text, iconURL} = footer;
footerFix = {
text: template(text, args)
};
if (iconURL !== undefined)
footerFix.iconURL = template(iconURL, args);
}
embed.setFooter(footerFix);
}
if (color !== undefined)
embed.setColor(resolveColor(template(color, args)));
if (description !== undefined)
embed.setDescription(template(bigString(description), args));
if (image !== undefined)
embed.setImage(template(image, args));
if (thumbnail !== undefined)
embed.setThumbnail(template(thumbnail, args));
if (title !== undefined)
embed.setTitle(template(title, args));
if (url !== undefined)
embed.setURL(template(url, args));
if (timestamp === true)
embed.setTimestamp();
else if (typeof timestamp === 'string')
embed.setTimestamp(new Date(template(timestamp, args)));
else if (timestamp !== false)
embed.setTimestamp(timestamp);
fields?.forEach(field => {
embed.addField(template(field.name, args), template(bigString(field.value), args), field.inline);
});
return embed;
}

9
src/discord/types.ts Normal file
View File

@@ -0,0 +1,9 @@
import { MessageEmbed } from "discord.js";
/**
* an object that contains embeds and can be passed directly to methods like `Discord.TextChannel.send()`
*/
export interface embedDataType {
content?: string,
embeds: MessageEmbed[]
}

76
src/main.ts Normal file
View File

@@ -0,0 +1,76 @@
import { authorData, basicObject, basicObjectString, bigStringType, embedObject, footerData } from './types';
/**
*
* @param str
* @param args
* @returns
*/
export function template(str: string, args: basicObject): string {
return str.replace(/{\w+}/g, str => {
const key = str.substring(1, str.length-1);
if (key in args)
return args[key];
return key;
});
}
/**
* converts bigString to string
*/
export function bigString(bigStr: bigStringType): string {
if (Array.isArray(bigStr))
return bigStr.join('\n');
return bigStr;
}
/**
* converts Hex Color string to an RGB array
*/
export function resolveColor(color: string): [number, number, number] {
color = color.replace(/[^0-9a-f]/gi, '');
const colorNum: [number, number, number] = [0, 0, 0];
if (color.length === 3 || color.length === 6) {
const colorSplRaw = /([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/.exec(color);
if (!colorSplRaw)
return colorNum;
const colorSpl = colorSplRaw.slice(1, 4);
if (color.length === 3)
colorSpl.map(c => c + c);
for (let i = 0; i < colorSpl.length && i < colorNum.length; i++)
colorNum[i] = parseInt(colorSpl[i], 16);
}
return colorNum;
}
/**
* converts embedObj to a string if applicable
* @param fallback the string to use if no valid strings can be found
*/
export function embedObjStr(embedObj: embedObject, args: basicObjectString = {}, fallback = ''): string {
if (embedObj.content !== undefined)
return template(bigString(embedObj.content), args);
if (embedObj.description !== undefined)
return template(bigString(embedObj.description), args);
return fallback;
}

99
src/types.ts Normal file
View File

@@ -0,0 +1,99 @@
/**
* any indexable object
*/
//this is a generic type, and needs 'any'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type basicObject = {[keys: string]: any};
/**
* any indexable object with string values
*/
export type basicObjectStringable = {[keys: string]: string|number|boolean|null};
export type basicObjectString = {[keys: string]: string};
/**
* an abstract version of strings
*/
export type bigStringType = string | string[];
/**
* a representation of an author in the LANG object
*
* `LANG > Language > Embed > Field`
*/
export interface embedField {
name: string,
value: bigStringType,
inline?: boolean
}
/**
* a representation of an author in the LANG object
*
* `LANG > Language > Embed > Author`
*/
export interface authorData {
name: string,
url?: string,
iconURL?: string
}
/**
* a representation of a footer in the LANG object
*
* `LANG > Language > Embed > Footer`
*/
export interface footerData {
text: string,
iconURL?: string
}
/**
* a representation of an embed in the LANG object
*
* `LANG > Language > Embed`
*/
export interface embedObject {
embed: true,
content?: string,
title?: string,
description?: bigStringType,
/**
* URL
*/
url?: string,
/**
* #FFFFFF
*/
color?: string,
footer?: string | footerData,
thumbnail?: string,
/**
* URL
*/
image?: string,
/**
* URL
*/
author?: string | authorData,
fields?: embedField[],
timestamp?: boolean | string | number
}
/**
* a specific language in the LANG object
*
* `LANG > Language`
*/
export type LangObj = { [keys:string]: LangObj | embedObject | string }
/**
* the entire LANG object
*
* `LANG`
*/
export type LangObjWhole = { [langid:string]: LangObj }

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules"]
}