aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/arguments/duration.ts13
-rw-r--r--src/arguments/permission.ts12
-rw-r--r--src/commands/admin/channelPermissions.ts8
-rw-r--r--src/commands/config/blacklist.ts4
-rw-r--r--src/commands/config/disable.ts4
-rw-r--r--src/commands/dev/__template.ts4
-rw-r--r--src/commands/dev/sh.ts5
-rw-r--r--src/commands/dev/superUser.ts6
-rw-r--r--src/commands/dev/test.ts5
-rw-r--r--src/commands/info/avatar.ts4
-rw-r--r--src/commands/info/color.ts2
-rw-r--r--src/commands/info/guildInfo.ts4
-rw-r--r--src/commands/info/userInfo.ts2
-rw-r--r--src/commands/moderation/_lockdown.ts2
-rw-r--r--src/commands/moderation/ban.ts4
-rw-r--r--src/commands/moderation/modlog.ts3
-rw-r--r--src/commands/moderation/mute.ts2
-rw-r--r--src/commands/moderation/removeReactionEmoji.ts3
-rw-r--r--src/commands/moderation/role.ts2
-rw-r--r--src/commands/moderation/slowmode.ts2
-rw-r--r--src/commands/moderation/warn.ts1
-rw-r--r--src/commands/moulberry-bush/capePerms.ts4
-rw-r--r--src/commands/moulberry-bush/report.ts8
-rw-r--r--src/commands/moulberry-bush/rule.ts5
-rw-r--r--src/commands/skyblock-reborn/chooseColor.ts3
-rw-r--r--src/commands/utilities/decode.ts4
-rw-r--r--src/commands/utilities/hash.ts4
-rw-r--r--src/commands/utilities/price.ts7
-rw-r--r--src/commands/utilities/uuid.ts5
-rw-r--r--src/commands/utilities/viewraw.ts10
-rw-r--r--src/lib/badwords.json3
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts120
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts81
-rw-r--r--src/listeners/message/automodCreate.ts19
-rw-r--r--yarn.lock49
37 files changed, 283 insertions, 137 deletions
diff --git a/package.json b/package.json
index ab53e62..41b4630 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
"chalk": "^4.1.1",
"common-tags": "^1.8.0",
"discord-akairo": "NotEnoughUpdates/discord-akairo",
- "discord-api-types": "0.19.0",
+ "discord-api-types": "0.20.2",
"discord.js": "NotEnoughUpdates/discord.js",
"discord.js-minesweeper": "^1.0.6",
"fuse.js": "^6.4.6",
diff --git a/src/arguments/duration.ts b/src/arguments/duration.ts
index 53b200e..ce181e8 100644
--- a/src/arguments/duration.ts
+++ b/src/arguments/duration.ts
@@ -1,18 +1,5 @@
import { BushArgumentTypeCaster, BushMessage } from '@lib';
export const durationTypeCaster: BushArgumentTypeCaster = (_message: BushMessage, phrase): number => {
- // if (!phrase) return null;
- // const regexString = Object.entries(BushConstants.TimeUnits)
- // .map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`)
- // .join('\\s*');
- // const match = new RegExp(`^${regexString}$`, 'im').exec(phrase);
- // if (!match) return null;
- // let milliseconds = 0;
- // for (const key in match.groups) {
- // const value = Number(match.groups[key] || 0);
- // milliseconds += value * BushConstants.TimeUnits[key].value;
- // }
- // return milliseconds;
-
return client.util.parseDuration(phrase).duration;
};
diff --git a/src/arguments/permission.ts b/src/arguments/permission.ts
new file mode 100644
index 0000000..dc90f3a
--- /dev/null
+++ b/src/arguments/permission.ts
@@ -0,0 +1,12 @@
+import { Permissions } from 'discord.js';
+import { BushArgumentTypeCaster } from '../lib/extensions/discord-akairo/BushArgumentTypeCaster';
+
+export const permissionTypeCaster: BushArgumentTypeCaster = (_, phrase) => {
+ if (!phrase) return null;
+ phrase = phrase.toUpperCase().replace(/ /g, '_');
+ if (!Permissions.FLAGS[phrase]) {
+ return null;
+ } else {
+ return phrase;
+ }
+};
diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts
index 08d0068..1cc44ca 100644
--- a/src/commands/admin/channelPermissions.ts
+++ b/src/commands/admin/channelPermissions.ts
@@ -1,4 +1,3 @@
-import { Argument, Constants } from 'discord-akairo';
import { GuildChannel, GuildMember, MessageEmbed, Role } from 'discord.js';
import { BushCommand, BushMessage } from '../../lib';
@@ -16,8 +15,7 @@ export default class ChannelPermissionsCommand extends BushCommand {
args: [
{
id: 'target',
- type: Argument.union(Constants.ArgumentTypes.ROLE, Constants.ArgumentTypes.MEMBER),
- match: Constants.ArgumentMatches.PHRASE,
+ customType: util.arg.union('member', 'member'),
prompt: {
start: 'What user/role would you like to change?',
retry: 'Invalid response. What user/role would you like to change?'
@@ -26,7 +24,6 @@ export default class ChannelPermissionsCommand extends BushCommand {
{
id: 'permission',
type: 'permission',
- match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'What permission would you like to change?',
retry: '{error} Choose a valid permission.'
@@ -34,12 +31,11 @@ export default class ChannelPermissionsCommand extends BushCommand {
},
{
id: 'state',
- type: [
+ customType: [
['true', '1', 'yes', 'enable', 'allow'],
['false', '0', 'no', 'disable', 'disallow', 'deny'],
['neutral', 'remove', 'none']
],
- match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'What should that permission be set to?',
retry: '{error} Set the state to either `enable`, `disable`, or `remove`.'
diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts
index 8093d83..150a1b7 100644
--- a/src/commands/config/blacklist.ts
+++ b/src/commands/config/blacklist.ts
@@ -15,8 +15,8 @@ export default class BlacklistCommand extends BushCommand {
args: [
{
id: 'target',
- type: Argument.union('channel', 'user'),
- match: 'phrase',
+ customType: Argument.union('channel', 'user'),
+
prompt: {
start: 'What channel or user that you would like to blacklist/unblacklist?',
retry: '{error} Pick a valid command.',
diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts
index 41ca8a4..5d2e4dd 100644
--- a/src/commands/config/disable.ts
+++ b/src/commands/config/disable.ts
@@ -1,5 +1,4 @@
import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, Global } from '@lib';
-import { Argument } from 'discord-akairo';
export default class DisableCommand extends BushCommand {
public constructor() {
@@ -14,8 +13,7 @@ export default class DisableCommand extends BushCommand {
args: [
{
id: 'command',
- type: Argument.union('commandAlias', 'command'),
- match: 'phrase',
+ customType: util.arg.union('commandAlias', 'command'),
prompt: {
start: 'What command would you like to enable/disable?',
retry: '{error} Pick a valid command.',
diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts
index 1e65457..35c57db 100644
--- a/src/commands/dev/__template.ts
+++ b/src/commands/dev/__template.ts
@@ -14,7 +14,7 @@ export default class TemplateCommand extends BushCommand {
{
id: 'required_argument',
type: 'string',
- match: 'phrase',
+
prompt: {
start: 'What would you like to set your first argument to be?',
retry: '{error} Pick a valid argument.',
@@ -24,7 +24,7 @@ export default class TemplateCommand extends BushCommand {
{
id: 'optional_argument',
type: 'string',
- match: 'phrase',
+
prompt: {
start: 'What would you like to set your second argument to be?',
retry: '{error} Pick a valid argument.',
diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts
index ed1dfd7..b8c8379 100644
--- a/src/commands/dev/sh.ts
+++ b/src/commands/dev/sh.ts
@@ -1,7 +1,6 @@
import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
import chalk from 'chalk';
import { exec } from 'child_process';
-import { Constants } from 'discord-akairo';
import { MessageEmbed, Util } from 'discord.js';
import { promisify } from 'util';
@@ -25,8 +24,8 @@ export default class ShCommand extends BushCommand {
args: [
{
id: 'command',
- type: Constants.ArgumentTypes.STRING,
- match: Constants.ArgumentMatches.REST,
+ type: 'string',
+ match: 'rest',
prompt: {
start: 'What would you like run',
retry: '{error} Invalid command to run.'
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index 9071a8d..eade861 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -1,5 +1,4 @@
import { BushCommand, BushMessage, BushSlashMessage, Global } from '@lib';
-import { Constants } from 'discord-akairo';
import { User } from 'discord.js';
export default class SuperUserCommand extends BushCommand {
@@ -20,7 +19,6 @@ export default class SuperUserCommand extends BushCommand {
const action = yield {
id: 'action',
type: ['add', 'remove'],
- match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'Would you like to `add` or `remove` a user from the superuser list?',
retry: '{error} Choose if you would like to `add` or `remove` a user.',
@@ -29,8 +27,8 @@ export default class SuperUserCommand extends BushCommand {
};
const user = yield {
id: 'user',
- type: Constants.ArgumentTypes.USER,
- match: Constants.ArgumentMatches.REST_CONTENT,
+ type: 'user',
+ match: 'restContent',
prompt: {
start: `Who would you like to ${action || 'add/remove'} from the superuser list?`,
retry: `Choose a valid user to ${action || 'add/remove'} from the superuser list.`,
diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts
index 3a27be0..6b49dce 100644
--- a/src/commands/dev/test.ts
+++ b/src/commands/dev/test.ts
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BushCommand, BushMessage } from '@lib';
-import { Constants } from 'discord-akairo';
import { Constants as jsConstants, MessageActionRow, MessageButton, MessageEmbed } from 'discord.js';
export default class TestCommand extends BushCommand {
@@ -18,8 +17,8 @@ export default class TestCommand extends BushCommand {
args: [
{
id: 'feature',
- type: Constants.ArgumentTypes.STRING,
- match: Constants.ArgumentMatches.REST,
+ type: 'string',
+ match: 'rest',
prompt: {
start: 'start prompt',
retry: 'retry prompt',
diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts
index 4f7449b..eefdc02 100644
--- a/src/commands/info/avatar.ts
+++ b/src/commands/info/avatar.ts
@@ -1,4 +1,3 @@
-import { Constants } from 'discord-akairo';
import { MessageEmbed, User } from 'discord.js';
import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
@@ -15,8 +14,7 @@ export default class AvatarCommand extends BushCommand {
args: [
{
id: 'user',
- type: Constants.ArgumentTypes.USER,
- match: Constants.ArgumentMatches.PHRASE,
+ type: 'user',
prompt: {
start: 'Who would you like to see the avatar of?',
retry: '{error} Choose a valid user.',
diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts
index 61790ad..e995d79 100644
--- a/src/commands/info/color.ts
+++ b/src/commands/info/color.ts
@@ -24,7 +24,7 @@ export default class ColorCommand extends BushCommand {
args: [
{
id: 'color',
- type: Argument.union(isValidTinyColor, 'role', 'member'),
+ customType: Argument.union(isValidTinyColor, 'role', 'member'),
prompt: {
start: 'What color code, role, or user would you like to find the color of?',
retry: '{error} Choose a valid color, role, or member.'
diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts
index 577086b..6ce4d76 100644
--- a/src/commands/info/guildInfo.ts
+++ b/src/commands/info/guildInfo.ts
@@ -1,4 +1,3 @@
-import { Argument, Constants } from 'discord-akairo';
import { BaseGuildVoiceChannel, Guild, GuildPreview, MessageEmbed, Snowflake, Vanity } from 'discord.js';
import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
@@ -15,8 +14,7 @@ export default class GuildInfoCommand extends BushCommand {
args: [
{
id: 'guild',
- type: Argument.union(Constants.ArgumentTypes.GUILD, Constants.ArgumentTypes.BIGINT),
- match: Constants.ArgumentMatches.PHRASE,
+ customType: util.arg.union('guild', 'bigint'),
prompt: {
start: 'What server would you like to find information about?',
retry: '{error} Choose a valid server to find information about.',
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
index e36e92b..cecf5ec 100644
--- a/src/commands/info/userInfo.ts
+++ b/src/commands/info/userInfo.ts
@@ -1,7 +1,7 @@
import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
import { GuildMember, MessageEmbed } from 'discord.js';
-// TODO: Allow looking up a user not in the guild and not cached
+// TODO: Allow looking up a user not in the guild and not cached (if possible)
// TODO: Re-Implement Status Emojis
// TODO: Add bot information
export default class UserInfoCommand extends BushCommand {
diff --git a/src/commands/moderation/_lockdown.ts b/src/commands/moderation/_lockdown.ts
index db074b1..df5a9b4 100644
--- a/src/commands/moderation/_lockdown.ts
+++ b/src/commands/moderation/_lockdown.ts
@@ -13,7 +13,7 @@ export default class LockdownCommand extends BushCommand {
args: [
{
id: 'all',
- type: 'flag',
+ match: 'flag',
flag: '--all'
}
],
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 3736165..9239f2a 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -10,7 +10,7 @@ export default class BanCommand extends BushCommand {
description: {
content: 'Ban a member from the server.',
usage: 'ban <member> <reason> [--delete ]',
- examples: ['ban 322862723090219008 1 day commands in #general --delete 7']
+ examples: ['ban ironm00n 1 day commands in #general --delete 7']
},
args: [
{
@@ -35,7 +35,7 @@ export default class BanCommand extends BushCommand {
id: 'days',
flag: '--days',
match: 'option',
- type: Argument.range('integer', 0, 7, true),
+ customType: util.arg.range('integer', 0, 7, true),
default: 0
},
{
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index c55b9a3..4850a4d 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -1,5 +1,4 @@
import { BushCommand, BushMessage, BushSlashMessage, BushUser, ModLog } from '@lib';
-import { Argument } from 'discord-akairo';
import { MessageEmbed, User } from 'discord.js';
export default class ModlogCommand extends BushCommand {
@@ -15,7 +14,7 @@ export default class ModlogCommand extends BushCommand {
args: [
{
id: 'search',
- type: Argument.union('user', 'string'),
+ customType: util.arg.union('user', 'string'),
prompt: {
start: 'What case id or user would you like to see?',
retry: '{error} Choose a valid case id or user.'
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 31cd233..7f3edbb 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -9,7 +9,7 @@ export default class MuteCommand extends BushCommand {
description: {
content: 'Mute a user.',
usage: 'mute <member> [reason] [duration]',
- examples: ['mute 322862723090219008 1 day commands in #general']
+ examples: ['mute ironm00n 1 day commands in #general']
},
args: [
{
diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts
index cb1be04..de5e8ce 100644
--- a/src/commands/moderation/removeReactionEmoji.ts
+++ b/src/commands/moderation/removeReactionEmoji.ts
@@ -1,5 +1,4 @@
import { BushCommand, BushMessage } from '@lib';
-import { Argument } from 'discord-akairo';
import { Emoji } from 'discord.js';
export default class RemoveReactionEmojiCommand extends BushCommand {
@@ -25,7 +24,7 @@ export default class RemoveReactionEmojiCommand extends BushCommand {
},
{
id: 'emoji',
- type: Argument.union('emoji', 'bigint'),
+ customType: util.arg.union('emoji', 'bigint'),
match: 'restContent',
prompt: {
start: 'What emoji would you like to remove?',
diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts
index fe8724d..bd8cf8d 100644
--- a/src/commands/moderation/role.ts
+++ b/src/commands/moderation/role.ts
@@ -14,7 +14,7 @@ export default class RoleCommand extends BushCommand {
args: [
{
id: 'action',
- type: [['add'], ['remove']],
+ customType: [['add'], ['remove']],
prompt: {
start: 'Would you like to `add` or `remove` a role?',
retry: '{error} Choose whether you would you like to `add` or `remove` a role.'
diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts
index 9b0d300..441a0ac 100644
--- a/src/commands/moderation/slowmode.ts
+++ b/src/commands/moderation/slowmode.ts
@@ -15,7 +15,7 @@ export default class SlowModeCommand extends BushCommand {
args: [
{
id: 'length',
- type: Argument.union('duration', 'off', 'none', 'disable'),
+ customType: Argument.union('duration', 'off', 'none', 'disable'),
default: 0,
prompt: {
start: 'What would you like to set the slowmode to?',
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index c5b4f43..3f9e9b5 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -21,7 +21,6 @@ export default class WarnCommand extends BushCommand {
},
{
id: 'reason',
- type: 'content',
match: 'rest',
prompt: {
start: 'Why should this user be warned?',
diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts
index d613217..7a021ed 100644
--- a/src/commands/moulberry-bush/capePerms.ts
+++ b/src/commands/moulberry-bush/capePerms.ts
@@ -1,5 +1,4 @@
import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
-import { Constants } from 'discord-akairo';
import { MessageEmbed } from 'discord.js';
import got from 'got';
@@ -16,8 +15,7 @@ export default class CapePermissionsCommand extends BushCommand {
args: [
{
id: 'ign',
- type: Constants.ArgumentTypes.STRING,
- match: Constants.ArgumentMatches.PHRASE,
+ type: 'string',
prompt: {
start: 'Who would you like to see the cape permissions of?',
retry: '{error} Choose someone to see the capes their available capes.',
diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts
index 4c6adc1..2ee02bd 100644
--- a/src/commands/moulberry-bush/report.ts
+++ b/src/commands/moulberry-bush/report.ts
@@ -1,4 +1,3 @@
-import { Constants } from 'discord-akairo';
import { GuildMember, MessageEmbed, TextChannel } from 'discord.js';
import moment from 'moment';
import { AllowedMentions, BushCommand, BushMessage } from '../../lib';
@@ -16,8 +15,7 @@ export default class ReportCommand extends BushCommand {
args: [
{
id: 'member',
- type: Constants.ArgumentTypes.MEMBER,
- match: Constants.ArgumentMatches.PHRASE,
+ type: 'member',
prompt: {
start: 'Who would you like to report?',
retry: `{error} Choose a valid user to report.`,
@@ -26,8 +24,8 @@ export default class ReportCommand extends BushCommand {
},
{
id: 'evidence',
- type: Constants.ArgumentTypes.STRING,
- match: Constants.ArgumentMatches.REST,
+ type: 'string',
+ match: 'rest',
prompt: {
start: 'What evidence do you have?',
retry: `{error} Provide what did they do wrong.`,
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index 1681a1b..41dd8de 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -1,4 +1,3 @@
-import { Argument, Constants } from 'discord-akairo';
import { MessageEmbed, User } from 'discord.js';
import { AllowedMentions, BushCommand, BushMessage } from '../../lib';
@@ -65,8 +64,7 @@ export default class RuleCommand extends BushCommand {
args: [
{
id: 'rule',
- type: Argument.range(Constants.ArgumentTypes.INTEGER, 1, rules.length, true),
- match: Constants.ArgumentMatches.PHRASE,
+ customType: util.arg.range('integer', 1, rules.length, true),
prompt: {
start: 'What rule would you like to have cited?',
retry: '{error} Choose a valid rule.',
@@ -76,7 +74,6 @@ export default class RuleCommand extends BushCommand {
{
id: 'user',
type: 'user',
- match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'What user would you like to mention?',
retry: '{error} Choose a valid user to mention.',
diff --git a/src/commands/skyblock-reborn/chooseColor.ts b/src/commands/skyblock-reborn/chooseColor.ts
index 2b72301..9b4b7c9 100644
--- a/src/commands/skyblock-reborn/chooseColor.ts
+++ b/src/commands/skyblock-reborn/chooseColor.ts
@@ -1,5 +1,4 @@
import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage } from '@lib';
-import { Constants } from 'discord-akairo';
import { CommandInteraction, Role, RoleResolvable, Snowflake } from 'discord.js';
const roleColorMap = [
@@ -94,7 +93,7 @@ export default class ChooseColorCommand extends BushCommand {
{
id: 'color',
type: 'role',
- match: Constants.ArgumentMatches.REST,
+ match: 'rest',
prompt: {
start: 'Please choose a valid color.',
retry: `{error} Provide what did they do wrong.`,
diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts
index aa3d455..cab23d8 100644
--- a/src/commands/utilities/decode.ts
+++ b/src/commands/utilities/decode.ts
@@ -18,7 +18,7 @@ export default class DecodeCommand extends BushCommand {
args: [
{
id: 'from',
- type: encodingTypesArray,
+ customType: encodingTypesArray,
prompt: {
start: 'What is the encoding of the original data?',
retry: `{error} Choose one of the following ${encodingTypesString} for the encoding of the original data.`
@@ -26,7 +26,7 @@ export default class DecodeCommand extends BushCommand {
},
{
id: 'to',
- type: encodingTypesArray,
+ customType: encodingTypesArray,
prompt: {
start: 'What would you like the encoding of the resulting data to be?',
retry: `{error} Choose one of the following ${encodingTypesString} for the encoding of the resulting data.`
diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts
index 4b5b01c..fcf2da3 100644
--- a/src/commands/utilities/hash.ts
+++ b/src/commands/utilities/hash.ts
@@ -1,5 +1,4 @@
import crypto from 'crypto';
-import { Constants } from 'discord-akairo';
import got from 'got';
import { BushCommand, BushMessage } from '../../lib';
@@ -16,8 +15,7 @@ export default class HashCommand extends BushCommand {
args: [
{
id: 'url',
- type: Constants.ArgumentTypes.URL,
- match: Constants.ArgumentMatches.PHRASE,
+ type: 'url',
prompt: {
start: 'What url would you like to find the hash of?',
retry: '{error} Enter a valid url.'
diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts
index d04544b..38cd8ca 100644
--- a/src/commands/utilities/price.ts
+++ b/src/commands/utilities/price.ts
@@ -1,4 +1,3 @@
-import { Constants } from 'discord-akairo';
import { MessageEmbed } from 'discord.js';
import Fuse from 'fuse.js';
import fetch from 'node-fetch';
@@ -66,8 +65,8 @@ export default class PriceCommand extends BushCommand {
args: [
{
id: 'item',
- match: Constants.ArgumentMatches.CONTENT,
- type: Constants.ArgumentTypes.STRING,
+ type: 'string',
+ match: 'content',
prompt: {
start: 'What item would you like to find the price of?',
retry: '{error} Choose a valid item.'
@@ -75,7 +74,7 @@ export default class PriceCommand extends BushCommand {
},
{
id: 'strict',
- match: Constants.ArgumentMatches.FLAG,
+ match: 'flag',
flag: '--strict',
default: false
}
diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts
index 7ff80d2..02ca506 100644
--- a/src/commands/utilities/uuid.ts
+++ b/src/commands/utilities/uuid.ts
@@ -1,4 +1,3 @@
-import { Constants } from 'discord-akairo';
import { BushCommand, BushMessage } from '../../lib';
export default class UuidCommand extends BushCommand {
@@ -14,8 +13,8 @@ export default class UuidCommand extends BushCommand {
args: [
{
id: 'ign',
- type: /\w{1,16}/im,
- match: Constants.ArgumentMatches.PHRASE,
+ customType: /\w{1,16}/im,
+
prompt: {
start: 'What ign would you like to find the uuid of?',
retry: '{error} Choose a valid ign.',
diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts
index ca5b2de..ca21b4b 100644
--- a/src/commands/utilities/viewraw.ts
+++ b/src/commands/utilities/viewraw.ts
@@ -1,4 +1,3 @@
-import { Argument, Constants } from 'discord-akairo';
import { DMChannel, Message, MessageEmbed, NewsChannel, Snowflake, TextChannel } from 'discord.js';
import { inspect } from 'util';
import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
@@ -17,8 +16,8 @@ export default class ViewRawCommand extends BushCommand {
args: [
{
id: 'message',
- type: Argument.union(Constants.ArgumentTypes.MESSAGE, Constants.ArgumentTypes.BIGINT),
- match: Constants.ArgumentMatches.PHRASE,
+ customType: util.arg.union('message', 'bigint'),
+
prompt: {
start: 'What message would you like to view?',
retry: '{error} Choose a valid message.',
@@ -27,8 +26,7 @@ export default class ViewRawCommand extends BushCommand {
},
{
id: 'channel',
- type: Constants.ArgumentTypes.CHANNEL,
- match: Constants.ArgumentMatches.PHRASE,
+ type: 'channel',
prompt: {
start: 'What channel is the message in?',
retry: '{error} Choose a valid channel.',
@@ -38,7 +36,7 @@ export default class ViewRawCommand extends BushCommand {
},
{
id: 'json',
- match: Constants.ArgumentMatches.FLAG,
+ match: 'flag',
flag: '--json'
}
]
diff --git a/src/lib/badwords.json b/src/lib/badwords.json
index 94c854f..6ea3618 100644
--- a/src/lib/badwords.json
+++ b/src/lib/badwords.json
@@ -11,5 +11,6 @@
"hi, bro h am leaving cs:go and giving away my skin": 3,
"hi friend, today i am leaving this fucking game": 3,
"hi guys, i'm leaving this fucking game, take my": 3,
- "you can choose any skin for yourself": 3
+ "you can choose any skin for yourself": 3,
+ "ironmoon": 1
}
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 54b5250..7b270f6 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -20,6 +20,7 @@ import readline from 'readline';
import { Sequelize } from 'sequelize';
import { contentWithDurationTypeCaster } from '../../../arguments/contentWithDuration';
import { durationTypeCaster } from '../../../arguments/duration';
+import { permissionTypeCaster } from '../../../arguments/permission';
import { UpdateCacheTask } from '../../../tasks/updateCache';
import { ActivePunishment } from '../../models/ActivePunishment';
import { Global } from '../../models/Global';
@@ -228,7 +229,8 @@ export class BushClient extends AkairoClient {
});
this.commandHandler.resolver.addTypes({
duration: durationTypeCaster,
- contentWithDuration: contentWithDurationTypeCaster
+ contentWithDuration: contentWithDurationTypeCaster,
+ permission: permissionTypeCaster
});
// loads all the handlers
const loaders = {
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index b6c9c1f..aecc635 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import {
+ BushArgumentType,
BushCache,
BushClient,
BushConstants,
@@ -16,7 +17,7 @@ import {
ModLogType
} from '@lib';
import { exec } from 'child_process';
-import { ClientUtil } from 'discord-akairo';
+import { Argument, ArgumentTypeCaster, ClientUtil, Flag, ParsedValuePredicate, TypeResolver } from 'discord-akairo';
import { APIMessage } from 'discord-api-types';
import {
ButtonInteraction,
@@ -796,4 +797,121 @@ export class BushClientUtil extends ClientUtil {
public capitalizeFirstLetter(string: string): string {
return string.charAt(0)?.toUpperCase() + string.slice(1);
}
+
+ public arg = new (class Arg {
+ /**
+ * Casts a phrase to this argument's type.
+ * @param type - The type to cast to.
+ * @param resolver - The type resolver.
+ * @param message - Message that called the command.
+ * @param phrase - Phrase to process.
+ */
+ public cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> {
+ return Argument.cast(type, resolver, message, phrase);
+ }
+
+ /**
+ * Creates a type that is the left-to-right composition of the given types.
+ * If any of the types fails, the entire composition fails.
+ * @param types - Types to use.
+ */
+ public compose(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.compose(types);
+ }
+
+ /**
+ * Creates a type that is the left-to-right composition of the given types.
+ * If any of the types fails, the composition still continues with the failure passed on.
+ * @param types - Types to use.
+ */
+ public composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.composeWithFailure(types);
+ }
+
+ /**
+ * Checks if something is null, undefined, or a fail flag.
+ * @param value - Value to check.
+ */
+ public isFailure(value: any): value is null | undefined | (Flag & { value: any }) {
+ return Argument.isFailure(value);
+ }
+
+ /**
+ * Creates a type from multiple types (product type).
+ * Only inputs where each type resolves with a non-void value are valid.
+ * @param types - Types to use.
+ */
+ public product(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.product(types);
+ }
+
+ /**
+ * Creates a type where the parsed value must be within a range.
+ * @param type - The type to use.
+ * @param min - Minimum value.
+ * @param max - Maximum value.
+ * @param inclusive - Whether or not to be inclusive on the upper bound.
+ */
+ public range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster {
+ return Argument.range(type, min, max, inclusive);
+ }
+
+ /**
+ * Creates a type that parses as normal but also tags it with some data.
+ * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
+ */
+ public tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
+ return Argument.tagged(type, tag);
+ }
+
+ /**
+ * Creates a type from multiple types (union type).
+ * The first type that resolves to a non-void value is used.
+ * Each type will also be tagged using `tagged` with themselves.
+ * @param types - Types to use.
+ */
+ public taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.taggedUnion(types);
+ }
+
+ /**
+ * Creates a type that parses as normal but also tags it with some data and carries the original input.
+ * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
+ */
+ public taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster {
+ return Argument.taggedWithInput(type, tag);
+ }
+
+ /**
+ * Creates a type from multiple types (union type).
+ * The first type that resolves to a non-void value is used.
+ * @param types - Types to use.
+ */
+ public union(...types: BushArgumentType[]): ArgumentTypeCaster {
+ return Argument.union(types);
+ }
+
+ /**
+ * Creates a type with extra validation.
+ * If the predicate is not true, the value is considered invalid.
+ * @param type - The type to use.
+ * @param predicate - The predicate function.
+ */
+ public validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster {
+ return Argument.validate(type, predicate);
+ }
+
+ /**
+ * Creates a type that parses as normal but also carries the original input.
+ * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ */
+ public withInput(type: BushArgumentType): ArgumentTypeCaster {
+ return Argument.withInput(type);
+ }
+ })();
}
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 3f79aeb..6616d1d 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -1,20 +1,13 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
-import {
- ArgumentGenerator,
- ArgumentOptions,
- ArgumentPromptOptions,
- ArgumentTypeCaster,
- Command,
- CommandOptions
-} from 'discord-akairo';
+import { ArgumentOptions, ArgumentPromptOptions, ArgumentTypeCaster, Command, CommandOptions } from 'discord-akairo';
import { Snowflake } from 'discord.js';
import { BushMessage } from '../discord.js/BushMessage';
import { BushClient } from './BushClient';
import { BushCommandHandler } from './BushCommandHandler';
import { BushSlashMessage } from './BushSlashMessage';
-type BushArgumentType =
+export type BushArgumentType =
| 'string'
| 'lowercase'
| 'uppercase'
@@ -67,17 +60,74 @@ type BushArgumentType =
| 'command'
| 'inhibitor'
| 'listener'
- | 'duration';
-
-interface BaseBushArgumentOptions extends ArgumentOptions {
+ | 'duration'
+ | 'contentWithDuration'
+ | 'permission';
+interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type'> {
id: string;
description?: string;
prompt?: ArgumentPromptOptions;
}
export interface BushArgumentOptions extends BaseBushArgumentOptions {
+ /**
+ * The type that the argument should be cast to.
+ * - `string` does not cast to any type.
+ * - `lowercase` makes the input lowercase.
+ * - `uppercase` makes the input uppercase.
+ * - `charCodes` transforms the input to an array of char codes.
+ * - `number` casts to a number.
+ * - `integer` casts to an integer.
+ * - `bigint` casts to a big integer.
+ * - `url` casts to an `URL` object.
+ * - `date` casts to a `Date` object.
+ * - `color` casts a hex code to an integer.
+ * - `commandAlias` tries to resolve to a command from an alias.
+ * - `command` matches the ID of a command.
+ * - `inhibitor` matches the ID of an inhibitor.
+ * - `listener` matches the ID of a listener.
+ *
+ * Possible Discord-related types.
+ * These types can be plural (add an 's' to the end) and a collection of matching objects will be used.
+ * - `user` tries to resolve to a user.
+ * - `member` tries to resolve to a member.
+ * - `relevant` tries to resolve to a relevant user, works in both guilds and DMs.
+ * - `channel` tries to resolve to a channel.
+ * - `textChannel` tries to resolve to a text channel.
+ * - `voiceChannel` tries to resolve to a voice channel.
+ * - `stageChannel` tries to resolve to a stage channel.
+ * - `threadChannel` tries to resolve a thread channel.
+ * - `role` tries to resolve to a role.
+ * - `emoji` tries to resolve to a custom emoji.
+ * - `guild` tries to resolve to a guild.
+ * - `permission` tries to resolve to a permissions.
+ *
+ * Other Discord-related types:
+ * - `message` tries to fetch a message from an ID within the channel.
+ * - `guildMessage` tries to fetch a message from an ID within the guild.
+ * - `relevantMessage` is a combination of the above, works in both guilds and DMs.
+ * - `invite` tries to fetch an invite object from a link.
+ * - `userMention` matches a mention of a user.
+ * - `memberMention` matches a mention of a guild member.
+ * - `channelMention` matches a mention of a channel.
+ * - `roleMention` matches a mention of a role.
+ * - `emojiMention` matches a mention of an emoji.
+ *
+ * Misc:
+ * - `duration` tries to parse duration in milliseconds
+ * - `contentWithDuration` tries to parse duration in milliseconds and returns the remaining content with the duration
+ * removed
+ */
type?: BushArgumentType;
}
export interface CustomBushArgumentOptions extends BaseBushArgumentOptions {
+ /**
+ * An array of strings can be used to restrict input to only those strings, case insensitive.
+ * The array can also contain an inner array of strings, for aliases.
+ * If so, the first entry of the array will be used as the final argument.
+ *
+ * A regular expression can also be used.
+ * The evaluated argument will be an object containing the `match` and `matches` if global.
+ */
customType?: ArgumentTypeCaster | (string | string[])[] | RegExp | string;
}
@@ -90,7 +140,7 @@ export interface BushCommandOptions extends CommandOptions {
usage: string | string[];
examples: string | string[];
};
- args?: BushArgumentOptions[] | CustomBushArgumentOptions[] | ArgumentGenerator;
+ args?: BushArgumentOptions[] & CustomBushArgumentOptions[];
category: string;
completelyHide?: boolean;
}
@@ -124,7 +174,10 @@ export class BushCommand extends Command {
this.completelyHide = options.completelyHide;
if (options.args && typeof options.args !== 'function') {
options.args.forEach((arg: BushArgumentOptions | CustomBushArgumentOptions) => {
- if (arg['customType']) arg.type = arg['customType'];
+ if (arg['customType']) {
+ arg['type'] = arg['customType'];
+ delete arg['customType'];
+ }
});
}
}
diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts
index 0321aca..990632b 100644
--- a/src/listeners/message/automodCreate.ts
+++ b/src/listeners/message/automodCreate.ts
@@ -18,34 +18,31 @@ export default class AutomodMessageCreateListener extends BushListener {
}
public static async automod(message: BushMessage): Promise<unknown> {
- if (message.guild.id !== client.consts.mappings.guilds.bush) return; // just temporary
+ if (message.guild?.id !== client.consts.mappings.guilds.bush) return; // just temporary
/* await message.guild.getSetting('autoModPhases'); */
const badLinks = {};
_badLinks.forEach((link) => {
badLinks[link] = 3;
});
- // client.console.debug(badLinks, 1);
- // client.console.debug(badWords, 1);
-
- const wordArray = [...Object.keys(badWords), ...Object.keys(badLinks)];
+ const wordMap = { ...badWords, ...badLinks };
+ const wordKeys = Object.keys(wordMap);
const offences: { [key: string]: number } = {};
- // client.console.debug(wordArray);
- wordArray.forEach((word) => {
- const cleanMessageContent = message.content?.toLowerCase().replace(/ /g, '');
+ const cleanMessageContent = message.content?.toLowerCase().replace(/ /g, '');
+ wordKeys.forEach((word) => {
const cleanWord = word.toLowerCase().replace(/ /g, '');
- // client.console.debug(cleanMessageContent);
- // client.console.debug(cleanWord);
if (cleanMessageContent.includes(cleanWord)) {
- if (offences[word]) offences[word] = wordArray[word];
+ if (!offences[word]) offences[word] = wordMap[word];
}
});
if (!Object.keys(offences)?.length) return;
const highestOffence = Object.values(offences).sort((a, b) => b - a)[0];
+ client.console.debug(message.deletable);
+
switch (highestOffence) {
case 0: {
if (message.deletable) void message.delete();
diff --git a/yarn.lock b/yarn.lock
index 226bcb7..8469285 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -61,10 +61,10 @@ __metadata:
languageName: node
linkType: hard
-"@discordjs/collection@npm:^0.1.6":
- version: 0.1.6
- resolution: "@discordjs/collection@npm:0.1.6"
- checksum: a0ddf757098fa01a4d9d5812daf3c2ea88c1297618d346705e603642c2f19656188fbfb3d503ee77f2ee69e7a1c1bfe25932aec032692ee3c338335524886e05
+"@discordjs/collection@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "@discordjs/collection@npm:0.2.0"
+ checksum: 4f9fc995ca81eb981aa9a8c081f46da831db9cea69727f3f6a6b7690c30d237eb64b758c1fc3f3510a403cb55dce8aa618133a43391f241b218ac817abe1780b
languageName: node
linkType: hard
@@ -329,9 +329,9 @@ __metadata:
linkType: hard
"@types/node@npm:*":
- version: 16.4.7
- resolution: "@types/node@npm:16.4.7"
- checksum: a1e80deea93f10225d1ac0825bd5eff3355dbc37084a88b0138921c9c930eb54acebb008d090a332a96d31ff50de69f2f5fc1402571070ea745f023e9f874805
+ version: 16.4.8
+ resolution: "@types/node@npm:16.4.8"
+ checksum: 1d4a421b95ac4d7671de17c56969bf95943c252c410dbe33c380e97a8afdfa7c2e5dcda0063a5c24fcc5538f40ef33c2582ae37f004c3c666da3758d8c8eecfc
languageName: node
linkType: hard
@@ -805,7 +805,7 @@ __metadata:
chalk: ^4.1.1
common-tags: ^1.8.0
discord-akairo: NotEnoughUpdates/discord-akairo
- discord-api-types: 0.19.0
+ discord-api-types: 0.20.2
discord.js: NotEnoughUpdates/discord.js
discord.js-minesweeper: ^1.0.6
esbuild: ^0.12.11
@@ -1335,15 +1335,15 @@ __metadata:
discord-akairo@NotEnoughUpdates/discord-akairo:
version: 8.2.2
- resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=ce7f4a08d97bf28af7f35184de750b01b59dc34a"
- checksum: 89a0c720a14ddb32d588f9f043bf5f38ed86793f2bed51e5173185e20fbb87d334cc3c833e1c7804473d1e81b64e6c36f69df355fffd339544a4fa849b349b12
+ resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=c2618f883533b95741fdffc920f07728385be973"
+ checksum: dd754c904e6f872664a74609f0521d5949ca2503ae4dde5972808928f240b3ca981751f2d2e98c30fd6c62ce13959d633e3f7e7e02771cb05507a6cf46548ee3
languageName: node
linkType: hard
-"discord-api-types@npm:0.19.0, discord-api-types@npm:^0.19.0":
- version: 0.19.0
- resolution: "discord-api-types@npm:0.19.0"
- checksum: 83d9363772a0ac0fcfa9df61da1c510c5427c2d82cc4b1c2e0a8ab50c31b2ae297a416cd1559b21b039dcc8065e5dca962dbac12522ee5e3447e9f4ebcb8a846
+"discord-api-types@npm:0.20.2":
+ version: 0.20.2
+ resolution: "discord-api-types@npm:0.20.2"
+ checksum: e0903f90b27d082159a19f32e46c84f5b2133dd2c7640edc5b0dbd169a7a8678294dbe960a150efb6dce304c7347d73982255483b1e389309e8573263485e22a
languageName: node
linkType: hard
@@ -1354,6 +1354,13 @@ discord-akairo@NotEnoughUpdates/discord-akairo:
languageName: node
linkType: hard
+"discord-api-types@npm:^0.21.0":
+ version: 0.21.0
+ resolution: "discord-api-types@npm:0.21.0"
+ checksum: 00872f1c453964d86579e59a6aa57fc7b2025b84bf7168fb4dad1d8e218db09dc78c20758ef9e15135bbfc76c9bf51102e671c0a20530e552ad7fc6f93a971c3
+ languageName: node
+ linkType: hard
+
"discord.js-minesweeper@npm:^1.0.6":
version: 1.0.6
resolution: "discord.js-minesweeper@npm:1.0.6"
@@ -1363,18 +1370,18 @@ discord-akairo@NotEnoughUpdates/discord-akairo:
discord.js@NotEnoughUpdates/discord.js:
version: 13.0.0-dev
- resolution: "discord.js@https://github.com/NotEnoughUpdates/discord.js.git#commit=1ed8c6263c844719b42a821ca1f1c4081a20b68a"
+ resolution: "discord.js@https://github.com/NotEnoughUpdates/discord.js.git#commit=c453840a652e054e92d521106a1dec1255990511"
dependencies:
"@discordjs/builders": ^0.2.0
- "@discordjs/collection": ^0.1.6
+ "@discordjs/collection": ^0.2.0
"@discordjs/form-data": ^3.0.1
"@sapphire/async-queue": ^1.1.4
"@types/ws": ^7.4.5
abort-controller: ^3.0.0
- discord-api-types: ^0.19.0
+ discord-api-types: ^0.21.0
node-fetch: ^2.6.1
ws: ^7.5.1
- checksum: 4ea6042c37545f4048677e2fd865e76cf4718ebf404bb4f5e539e9f141978c76a402d1269618f43c879aec3b4e70fe88d1b82c43296eb6789dbee7d39f4e6d0f
+ checksum: 67f91118eb0d8ed90017dfb096377555c6df5bc2be1d7af70caaf02b3ddaffd10965da2454a887363e5f00a3d9c9ebb1d8a6d638c6946f7592d99dd217a50e21
languageName: node
linkType: hard
@@ -1557,8 +1564,8 @@ discord.js@NotEnoughUpdates/discord.js:
linkType: hard
"eslint@npm:^7.29.0":
- version: 7.31.0
- resolution: "eslint@npm:7.31.0"
+ version: 7.32.0
+ resolution: "eslint@npm:7.32.0"
dependencies:
"@babel/code-frame": 7.12.11
"@eslint/eslintrc": ^0.4.3
@@ -1602,7 +1609,7 @@ discord.js@NotEnoughUpdates/discord.js:
v8-compile-cache: ^2.0.3
bin:
eslint: bin/eslint.js
- checksum: fd73d07ce0b73e5ea950b295a6eaf8d45914b4e56cba4ef49e55a36dc7e965a4865f63f618c0a096a01d089752d9e44180b80ba8657039b8e631dd40e0af1663
+ checksum: cc85af9985a3a11085c011f3d27abe8111006d34cc274291b3c4d7bea51a4e2ff6135780249becd919ba7f6d6d1ecc38a6b73dacb6a7be08d38453b344dc8d37
languageName: node
linkType: hard