mirror of
https://github.com/sern-handler/sern-community
synced 2026-06-05 17:06:55 +00:00
no more ugly formatting
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"printWidth": 100
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4
|
||||
}
|
||||
|
||||
24
README.md
24
README.md
@@ -2,15 +2,15 @@
|
||||
|
||||
Features:
|
||||
|
||||
- Bofa
|
||||
- Thread support help
|
||||
- Message triggers
|
||||
- Autocomplete documentation for our [handler](https://github.com/sern-handler/handler)
|
||||
- Register your timezone to notify other's local time
|
||||
- Eval command
|
||||
- Hybrid commands
|
||||
- Switching discord prescenses
|
||||
- Games (rps, tic tac toe)
|
||||
- View our handler's [plugins](https://github.com/sern-handler/awesome-plugins)
|
||||
- Submit and review user emojis
|
||||
- Fetch Issues and Pull Requests from github
|
||||
- Bofa
|
||||
- Thread support help
|
||||
- Message triggers
|
||||
- Autocomplete documentation for our [handler](https://github.com/sern-handler/handler)
|
||||
- Register your timezone to notify other's local time
|
||||
- Eval command
|
||||
- Hybrid commands
|
||||
- Switching discord prescenses
|
||||
- Games (rps, tic tac toe)
|
||||
- View our handler's [plugins](https://github.com/sern-handler/awesome-plugins)
|
||||
- Submit and review user emojis
|
||||
- Fetch Issues and Pull Requests from github
|
||||
|
||||
92
package.json
92
package.json
@@ -1,48 +1,48 @@
|
||||
{
|
||||
"name": "sern-community",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "dist/src/index.js",
|
||||
"type": "module",
|
||||
"author": {
|
||||
"name": "EvolutionX-10",
|
||||
"url": "https://github.com/EvolutionX-10"
|
||||
},
|
||||
"imports": {
|
||||
"#plugins": "./dist/src/plugins/index.js",
|
||||
"#utils": "./dist/src/utils/index.js",
|
||||
"#constants": "./dist/src/constants.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "tsup && node .",
|
||||
"dev": "tsup && node --preserve-symlinks .",
|
||||
"watch": "tsup --watch --onSuccess \"node .\"",
|
||||
"compile": "tsup"
|
||||
},
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"sern",
|
||||
"discord.js"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/rest": "19.0.7",
|
||||
"@sern/handler": "^3.3.2",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "16.0.3",
|
||||
"jsdoc-parse-plus": "1.3.0",
|
||||
"string-similarity": "4.0.4",
|
||||
"tesseract.js": "^5.0.4",
|
||||
"trie-search": "1.4.1",
|
||||
"undici": "5.22.0",
|
||||
"winston": "3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.16.3",
|
||||
"@types/string-similarity": "4.0.0",
|
||||
"tsup": "6.7.0",
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"packageManager": "yarn@3.5.0"
|
||||
"name": "sern-community",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "dist/src/index.js",
|
||||
"type": "module",
|
||||
"author": {
|
||||
"name": "EvolutionX-10",
|
||||
"url": "https://github.com/EvolutionX-10"
|
||||
},
|
||||
"imports": {
|
||||
"#plugins": "./dist/src/plugins/index.js",
|
||||
"#utils": "./dist/src/utils/index.js",
|
||||
"#constants": "./dist/src/constants.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "tsup && node .",
|
||||
"dev": "tsup && node --preserve-symlinks .",
|
||||
"watch": "tsup --watch --onSuccess \"node .\"",
|
||||
"compile": "tsup"
|
||||
},
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"sern",
|
||||
"discord.js"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/rest": "19.0.7",
|
||||
"@sern/handler": "^3.3.2",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "16.0.3",
|
||||
"jsdoc-parse-plus": "1.3.0",
|
||||
"string-similarity": "4.0.4",
|
||||
"tesseract.js": "^5.0.4",
|
||||
"trie-search": "1.4.1",
|
||||
"undici": "5.22.0",
|
||||
"winston": "3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.16.3",
|
||||
"@types/string-similarity": "4.0.0",
|
||||
"tsup": "6.7.0",
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"packageManager": "yarn@3.5.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"language": "typescript",
|
||||
"paths": {
|
||||
"base": "src",
|
||||
"commands": "commands"
|
||||
}
|
||||
"language": "typescript",
|
||||
"paths": {
|
||||
"base": "src",
|
||||
"commands": "commands"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,99 +6,105 @@ import { publish } from "#plugins";
|
||||
import DocHandler from "../trie/doc-autocmp.js";
|
||||
|
||||
function handleComments(sum: PurpleSummary) {
|
||||
switch (sum.kind) {
|
||||
case Kind.Text:
|
||||
case Kind.Code:
|
||||
return { name: sum.kind, value: sum.text };
|
||||
case Kind.InlineTag:
|
||||
return {
|
||||
name: "Reference",
|
||||
value: `[${docHandler.DocTrie.search(sum.target!.toString())}](${sum.text})`,
|
||||
};
|
||||
}
|
||||
switch (sum.kind) {
|
||||
case Kind.Text:
|
||||
case Kind.Code:
|
||||
return { name: sum.kind, value: sum.text };
|
||||
case Kind.InlineTag:
|
||||
return {
|
||||
name: "Reference",
|
||||
value: `[${docHandler.DocTrie.search(sum.target!.toString())}](${sum.text})`,
|
||||
};
|
||||
}
|
||||
}
|
||||
const docHandler = new DocHandler();
|
||||
docHandler.setup();
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "Query documentation",
|
||||
plugins: [publish()],
|
||||
options: [
|
||||
{
|
||||
autocomplete: true,
|
||||
name: "search",
|
||||
required: true,
|
||||
description: "Search for the sern handler documentation",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(autocomplete) {
|
||||
const choices = docHandler.DocTrie.search(autocomplete.options.getFocused());
|
||||
return autocomplete.respond(
|
||||
choices.map((res) => ({ name: res.node.name, value: res.node.name })).slice(0, 25)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
execute: async (context, options) => {
|
||||
const option = options[1].getString("search", true);
|
||||
const result = docHandler.DocTrie.search(option);
|
||||
type: CommandType.Slash,
|
||||
description: "Query documentation",
|
||||
plugins: [publish()],
|
||||
options: [
|
||||
{
|
||||
autocomplete: true,
|
||||
name: "search",
|
||||
required: true,
|
||||
description: "Search for the sern handler documentation",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(autocomplete) {
|
||||
const choices = docHandler.DocTrie.search(autocomplete.options.getFocused());
|
||||
return autocomplete.respond(
|
||||
choices
|
||||
.map((res) => ({ name: res.node.name, value: res.node.name }))
|
||||
.slice(0, 25),
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
execute: async (context, options) => {
|
||||
const option = options[1].getString("search", true);
|
||||
const result = docHandler.DocTrie.search(option);
|
||||
|
||||
if (!result.length) {
|
||||
return context.reply("No results found");
|
||||
}
|
||||
const embeds = result.map((res) => {
|
||||
const comments =
|
||||
res.node.kindString === TentacledKindString.Function
|
||||
? res.node.signatures?.flatMap((dec) => {
|
||||
const summary = dec.comment?.summary as PurpleSummary[] | undefined;
|
||||
return summary?.map(handleComments) ?? [];
|
||||
})
|
||||
: res.node.comment?.summary?.map(handleComments);
|
||||
if (!result.length) {
|
||||
return context.reply("No results found");
|
||||
}
|
||||
const embeds = result.map((res) => {
|
||||
const comments =
|
||||
res.node.kindString === TentacledKindString.Function
|
||||
? res.node.signatures?.flatMap((dec) => {
|
||||
const summary = dec.comment?.summary as PurpleSummary[] | undefined;
|
||||
return summary?.map(handleComments) ?? [];
|
||||
})
|
||||
: res.node.comment?.summary?.map(handleComments);
|
||||
|
||||
let blockTags =
|
||||
res.node.kindString === TentacledKindString.Function
|
||||
? res.node.signatures?.flatMap((dec) => {
|
||||
const summary = dec.comment as PurpleComment | undefined;
|
||||
return (
|
||||
summary?.blockTags?.flatMap((btags) => {
|
||||
return btags.content.map((c) => ({
|
||||
name: btags.tag,
|
||||
value: c.text,
|
||||
}));
|
||||
}) ?? []
|
||||
);
|
||||
})
|
||||
: res.node?.comment?.blockTags?.flatMap((btags) => {
|
||||
return btags.content.map((c) => ({
|
||||
name: btags.tag,
|
||||
value: c.text,
|
||||
}));
|
||||
});
|
||||
let blockTags =
|
||||
res.node.kindString === TentacledKindString.Function
|
||||
? res.node.signatures?.flatMap((dec) => {
|
||||
const summary = dec.comment as PurpleComment | undefined;
|
||||
return (
|
||||
summary?.blockTags?.flatMap((btags) => {
|
||||
return btags.content.map((c) => ({
|
||||
name: btags.tag,
|
||||
value: c.text,
|
||||
}));
|
||||
}) ?? []
|
||||
);
|
||||
})
|
||||
: res.node?.comment?.blockTags?.flatMap((btags) => {
|
||||
return btags.content.map((c) => ({
|
||||
name: btags.tag,
|
||||
value: c.text,
|
||||
}));
|
||||
});
|
||||
|
||||
blockTags = blockTags?.map((tag) => {
|
||||
return {
|
||||
name: tag.name,
|
||||
value: tag.value.replace(/ title=(?:.+)./gm, ""),
|
||||
};
|
||||
});
|
||||
blockTags = blockTags?.map((tag) => {
|
||||
return {
|
||||
name: tag.name,
|
||||
value: tag.value.replace(/ title=(?:.+)./gm, ""),
|
||||
};
|
||||
});
|
||||
|
||||
return new EmbedBuilder()
|
||||
.addFields({ name: "Category", value: res.name }, ...(comments ?? []), ...(blockTags ?? []))
|
||||
.setTitle(`🔖 ${res.node.name}`)
|
||||
.setColor(Colors.DarkVividPink)
|
||||
.setAuthor({
|
||||
name: "sern",
|
||||
iconURL: context.client.user?.displayAvatarURL(),
|
||||
})
|
||||
.setURL(res.node.sources[0].url ?? "External implementation");
|
||||
});
|
||||
if(embeds.length === 1) {
|
||||
return context.reply({ embeds });
|
||||
}
|
||||
const paginator = new Paginator({ embeds });
|
||||
return new EmbedBuilder()
|
||||
.addFields(
|
||||
{ name: "Category", value: res.name },
|
||||
...(comments ?? []),
|
||||
...(blockTags ?? []),
|
||||
)
|
||||
.setTitle(`🔖 ${res.node.name}`)
|
||||
.setColor(Colors.DarkVividPink)
|
||||
.setAuthor({
|
||||
name: "sern",
|
||||
iconURL: context.client.user?.displayAvatarURL(),
|
||||
})
|
||||
.setURL(res.node.sources[0].url ?? "External implementation");
|
||||
});
|
||||
if (embeds.length === 1) {
|
||||
return context.reply({ embeds });
|
||||
}
|
||||
const paginator = new Paginator({ embeds });
|
||||
|
||||
return paginator.run(context.interaction);
|
||||
},
|
||||
return paginator.run(context.interaction);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,165 +1,166 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
Attachment,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
Message,
|
||||
Snowflake,
|
||||
TextChannel,
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
Attachment,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
Message,
|
||||
Snowflake,
|
||||
TextChannel,
|
||||
} from "discord.js";
|
||||
import { fetch } from "undici";
|
||||
import { cooldown, publish } from "#plugins";
|
||||
import { Resolver, slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
description: "Submit an emoji",
|
||||
plugins: [publish({ dmPermission: false }), cooldown.add([["user", "1/15"]])],
|
||||
options: [
|
||||
{
|
||||
name: "submit",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Submit an emoji",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "Name of the emoji",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "attachment",
|
||||
type: ApplicationCommandOptionType.Attachment,
|
||||
description: "An attachment for submission (under 256KB)",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "URL for submission",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute: async (ctx, [, args]) => {
|
||||
const command = args.getSubcommand();
|
||||
description: "Submit an emoji",
|
||||
plugins: [publish({ dmPermission: false }), cooldown.add([["user", "1/15"]])],
|
||||
options: [
|
||||
{
|
||||
name: "submit",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Submit an emoji",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "Name of the emoji",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "attachment",
|
||||
type: ApplicationCommandOptionType.Attachment,
|
||||
description: "An attachment for submission (under 256KB)",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "URL for submission",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute: async (ctx, [, args]) => {
|
||||
const command = args.getSubcommand();
|
||||
|
||||
await ctx.interaction.deferReply();
|
||||
switch (command) {
|
||||
case "submit": {
|
||||
const attachment = args.getAttachment("attachment");
|
||||
const urlString = args.getString("url");
|
||||
const name = args.getString("name", true);
|
||||
const send = sendTo("1014582281907753080", ctx.member as GuildMember, name);
|
||||
await ctx.interaction.deferReply();
|
||||
switch (command) {
|
||||
case "submit": {
|
||||
const attachment = args.getAttachment("attachment");
|
||||
const urlString = args.getString("url");
|
||||
const name = args.getString("name", true);
|
||||
const send = sendTo("1014582281907753080", ctx.member as GuildMember, name);
|
||||
|
||||
if (attachment) {
|
||||
const isValidAttachment = verify(
|
||||
attachment,
|
||||
(a) => a.size <= 256_000,
|
||||
(a) => a.contentType?.startsWith("image/") || false,
|
||||
(a) =>
|
||||
["image/png", "image/jpg", "image/gif"].includes(
|
||||
a.contentType ?? "Something that is not png or jpg when contentType is null"
|
||||
)
|
||||
);
|
||||
if (!isValidAttachment) {
|
||||
return ctx.interaction.editReply({
|
||||
content:
|
||||
"Your attachment is not in valid format or the size is over the limits of discord. Please submit something else",
|
||||
});
|
||||
}
|
||||
return ctx.interaction
|
||||
.editReply({
|
||||
content: "Thanks for submitting. Your emoji will now be reviewed",
|
||||
})
|
||||
.then((m) => send(m, attachment));
|
||||
} else if (urlString) {
|
||||
const url = new Resolver(urlString, ctx.interaction).url;
|
||||
if (!url || !(await validImage(url)))
|
||||
return ctx.interaction.editReply({
|
||||
content:
|
||||
"This URL is invalid or the size is over the limits of discord. Please submit something else",
|
||||
});
|
||||
return ctx.interaction
|
||||
.editReply({
|
||||
content: "Thanks for submitting. Your emoji will now be reviewed",
|
||||
})
|
||||
.then((m) => send(m, url));
|
||||
}
|
||||
if (attachment) {
|
||||
const isValidAttachment = verify(
|
||||
attachment,
|
||||
(a) => a.size <= 256_000,
|
||||
(a) => a.contentType?.startsWith("image/") || false,
|
||||
(a) =>
|
||||
["image/png", "image/jpg", "image/gif"].includes(
|
||||
a.contentType ??
|
||||
"Something that is not png or jpg when contentType is null",
|
||||
),
|
||||
);
|
||||
if (!isValidAttachment) {
|
||||
return ctx.interaction.editReply({
|
||||
content:
|
||||
"Your attachment is not in valid format or the size is over the limits of discord. Please submit something else",
|
||||
});
|
||||
}
|
||||
return ctx.interaction
|
||||
.editReply({
|
||||
content: "Thanks for submitting. Your emoji will now be reviewed",
|
||||
})
|
||||
.then((m) => send(m, attachment));
|
||||
} else if (urlString) {
|
||||
const url = new Resolver(urlString, ctx.interaction).url;
|
||||
if (!url || !(await validImage(url)))
|
||||
return ctx.interaction.editReply({
|
||||
content:
|
||||
"This URL is invalid or the size is over the limits of discord. Please submit something else",
|
||||
});
|
||||
return ctx.interaction
|
||||
.editReply({
|
||||
content: "Thanks for submitting. Your emoji will now be reviewed",
|
||||
})
|
||||
.then((m) => send(m, url));
|
||||
}
|
||||
|
||||
return ctx.interaction.editReply({
|
||||
content: "You gotta provide either attachment or a url mate!",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
return ctx.interaction.editReply({
|
||||
content: "You gotta provide either attachment or a url mate!",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function verify<T>(attachment: T, ...conditions: ((attachment: T) => boolean)[]) {
|
||||
return conditions.reduce((partial, func) => {
|
||||
return func(attachment) && partial;
|
||||
}, true);
|
||||
return conditions.reduce((partial, func) => {
|
||||
return func(attachment) && partial;
|
||||
}, true);
|
||||
}
|
||||
|
||||
async function validImage(url: URL) {
|
||||
return fetch(url.toString())
|
||||
.then((req) => req.blob())
|
||||
.then((req) => req.type.startsWith("image/") && req.size < 262144)
|
||||
.catch(() => false);
|
||||
return fetch(url.toString())
|
||||
.then((req) => req.blob())
|
||||
.then((req) => req.type.startsWith("image/") && req.size < 262144)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
function sendTo(channelId: Snowflake, member: GuildMember, name: string) {
|
||||
async function provider(context: Message, payload: Attachment): Promise<void>;
|
||||
async function provider(context: Message, payload: URL): Promise<void>;
|
||||
async function provider(context: Message, payload: Attachment | URL) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("Yellow")
|
||||
.setTitle("Emoji Suggestion")
|
||||
.setAuthor({
|
||||
name: member.user.tag,
|
||||
iconURL: member.displayAvatarURL(),
|
||||
url: context.url,
|
||||
})
|
||||
.addFields(
|
||||
{
|
||||
name: "Suggested Name",
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
name: "Status",
|
||||
value: "Pending Approval",
|
||||
}
|
||||
)
|
||||
.setTimestamp();
|
||||
async function provider(context: Message, payload: Attachment): Promise<void>;
|
||||
async function provider(context: Message, payload: URL): Promise<void>;
|
||||
async function provider(context: Message, payload: Attachment | URL) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("Yellow")
|
||||
.setTitle("Emoji Suggestion")
|
||||
.setAuthor({
|
||||
name: member.user.tag,
|
||||
iconURL: member.displayAvatarURL(),
|
||||
url: context.url,
|
||||
})
|
||||
.addFields(
|
||||
{
|
||||
name: "Suggested Name",
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
name: "Status",
|
||||
value: "Pending Approval",
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
const channel = (await context.client.channels.fetch(channelId)) as TextChannel;
|
||||
if (payload instanceof Attachment) {
|
||||
embed.setImage(`attachment://${payload.name}`);
|
||||
} else embed.setImage(payload.toString());
|
||||
const channel = (await context.client.channels.fetch(channelId)) as TextChannel;
|
||||
if (payload instanceof Attachment) {
|
||||
embed.setImage(`attachment://${payload.name}`);
|
||||
} else embed.setImage(payload.toString());
|
||||
|
||||
await channel.send({
|
||||
content: "Bruddas, please review, thank you.",
|
||||
files: payload instanceof Attachment ? [payload] : [],
|
||||
embeds: [embed],
|
||||
components: [gimmeRow()],
|
||||
});
|
||||
}
|
||||
return provider;
|
||||
await channel.send({
|
||||
content: "Bruddas, please review, thank you.",
|
||||
files: payload instanceof Attachment ? [payload] : [],
|
||||
embeds: [embed],
|
||||
components: [gimmeRow()],
|
||||
});
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
function gimmeRow() {
|
||||
const accept = new ButtonBuilder()
|
||||
.setCustomId("emoji/accept")
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setEmoji("✅");
|
||||
const deny = new ButtonBuilder()
|
||||
.setCustomId("emoji/deny")
|
||||
.setLabel("Deny")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("❌");
|
||||
return new ActionRowBuilder<ButtonBuilder>().setComponents(deny, accept);
|
||||
const accept = new ButtonBuilder()
|
||||
.setCustomId("emoji/accept")
|
||||
.setLabel("Accept")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setEmoji("✅");
|
||||
const deny = new ButtonBuilder()
|
||||
.setCustomId("emoji/deny")
|
||||
.setLabel("Deny")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("❌");
|
||||
return new ActionRowBuilder<ButtonBuilder>().setComponents(deny, accept);
|
||||
}
|
||||
|
||||
@@ -4,86 +4,89 @@ import { inspect } from "util";
|
||||
import { Evo, Seren, Mina } from "#constants";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Text,
|
||||
description: "Eval something",
|
||||
alias: ["ev"],
|
||||
execute: async (ctx, args) => {
|
||||
if (![Evo, Seren, Mina].includes(ctx.user.id)) return;
|
||||
type: CommandType.Text,
|
||||
description: "Eval something",
|
||||
alias: ["ev"],
|
||||
execute: async (ctx, args) => {
|
||||
if (![Evo, Seren, Mina].includes(ctx.user.id)) return;
|
||||
|
||||
let code: string[] | string = args[1];
|
||||
let code: string[] | string = args[1];
|
||||
|
||||
code = code.join(" ") as string;
|
||||
if (code.includes("await")) {
|
||||
const ar = code.split(";");
|
||||
const last = ar.pop();
|
||||
code = `(async () => {\n${ar.join(";\n")}\nreturn ${last?.trim() ?? " "}\n\n})();`;
|
||||
}
|
||||
const { channel, guild, client, user, member, message: msg } = ctx;
|
||||
if (
|
||||
["TOKEN", "process..env", "token"].some((e) => code.includes(e)) &&
|
||||
ctx.user.id !== "697795666373640213"
|
||||
)
|
||||
return ctx.message.react("❌");
|
||||
code = code.join(" ") as string;
|
||||
if (code.includes("await")) {
|
||||
const ar = code.split(";");
|
||||
const last = ar.pop();
|
||||
code = `(async () => {\n${ar.join(";\n")}\nreturn ${last?.trim() ?? " "}\n\n})();`;
|
||||
}
|
||||
const { channel, guild, client, user, member, message: msg } = ctx;
|
||||
if (
|
||||
["TOKEN", "process..env", "token"].some((e) => code.includes(e)) &&
|
||||
ctx.user.id !== "697795666373640213"
|
||||
)
|
||||
return ctx.message.react("❌");
|
||||
|
||||
let result: unknown | string;
|
||||
let result: unknown | string;
|
||||
|
||||
try {
|
||||
result = eval(code);
|
||||
} catch (error) {
|
||||
result = error;
|
||||
}
|
||||
if (result instanceof Promise) result = await result.catch((e: Error) => new Error(e.message));
|
||||
if (typeof result !== "string") {
|
||||
result = inspect(result, {
|
||||
depth: 0,
|
||||
});
|
||||
}
|
||||
try {
|
||||
result = eval(code);
|
||||
} catch (error) {
|
||||
result = error;
|
||||
}
|
||||
if (result instanceof Promise)
|
||||
result = await result.catch((e: Error) => new Error(e.message));
|
||||
if (typeof result !== "string") {
|
||||
result = inspect(result, {
|
||||
depth: 0,
|
||||
});
|
||||
}
|
||||
|
||||
result = "```js\n" + result + "\n```";
|
||||
result = "```js\n" + result + "\n```";
|
||||
|
||||
if ((result as string).length > 2000) {
|
||||
channel!.send("Result is too long to send");
|
||||
}
|
||||
if ((result as string).length > 2000) {
|
||||
channel!.send("Result is too long to send");
|
||||
}
|
||||
|
||||
ctx.channel!.send({ content: result as string });
|
||||
ctx.channel!.send({ content: result as string });
|
||||
|
||||
function send(id: string, ping = false) {
|
||||
const channel = client.channels.cache.get(id);
|
||||
if (!channel) return;
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xcc5279)
|
||||
.setTitle("v2 is out!")
|
||||
.setThumbnail(client.user?.displayAvatarURL() ?? "")
|
||||
.setImage("https://raw.githubusercontent.com/sern-handler/.github/main/banner.png")
|
||||
.setAuthor({ name: "sern", url: "https://sern.dev/" })
|
||||
.setDescription(`__**Quick Look:**__\n\n${text()}\n\nThank you all for being patient!`)
|
||||
.setFooter({ text: "Supports DJS v14.7 and above" })
|
||||
.setTimestamp();
|
||||
const content = ping ? "@everyone" : undefined;
|
||||
channel.isTextBased() && channel.send({ content, embeds: [embed] });
|
||||
return "Done sir";
|
||||
}
|
||||
},
|
||||
function send(id: string, ping = false) {
|
||||
const channel = client.channels.cache.get(id);
|
||||
if (!channel) return;
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xcc5279)
|
||||
.setTitle("v2 is out!")
|
||||
.setThumbnail(client.user?.displayAvatarURL() ?? "")
|
||||
.setImage("https://raw.githubusercontent.com/sern-handler/.github/main/banner.png")
|
||||
.setAuthor({ name: "sern", url: "https://sern.dev/" })
|
||||
.setDescription(
|
||||
`__**Quick Look:**__\n\n${text()}\n\nThank you all for being patient!`,
|
||||
)
|
||||
.setFooter({ text: "Supports DJS v14.7 and above" })
|
||||
.setTimestamp();
|
||||
const content = ping ? "@everyone" : undefined;
|
||||
channel.isTextBased() && channel.send({ content, embeds: [embed] });
|
||||
return "Done sir";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function text() {
|
||||
const obj = [
|
||||
{
|
||||
name: `[CLI](https://github.com/sern-handler/cli):`,
|
||||
value: `\` - \` Updated templates for v2`,
|
||||
},
|
||||
{
|
||||
name: `[@sern/handler](https://www.npmjs.com/package/@sern/handler):`,
|
||||
value: `\` - \` Read blog (I wrote everything here) https://sern.dev/blog`,
|
||||
},
|
||||
{
|
||||
name: `[Website](https://sern.dev)`,
|
||||
value: `\` - \` Blog 2.0`,
|
||||
},
|
||||
// {
|
||||
// name: `[Community bot](https://github.com/sern-handler/sern-community)`,
|
||||
// value: `\` - \` Documentation at your hands in this server!\n\` - \` Autocompletes\n\` - \` Tag System\n\` - \` Features all the plugins in [this repository](https://github.com/sern-handler/awesome-plugins)`,
|
||||
// },
|
||||
];
|
||||
return obj.map(({ name, value }) => `**${name}**\n${value}`).join("\n\n");
|
||||
const obj = [
|
||||
{
|
||||
name: `[CLI](https://github.com/sern-handler/cli):`,
|
||||
value: `\` - \` Updated templates for v2`,
|
||||
},
|
||||
{
|
||||
name: `[@sern/handler](https://www.npmjs.com/package/@sern/handler):`,
|
||||
value: `\` - \` Read blog (I wrote everything here) https://sern.dev/blog`,
|
||||
},
|
||||
{
|
||||
name: `[Website](https://sern.dev)`,
|
||||
value: `\` - \` Blog 2.0`,
|
||||
},
|
||||
// {
|
||||
// name: `[Community bot](https://github.com/sern-handler/sern-community)`,
|
||||
// value: `\` - \` Documentation at your hands in this server!\n\` - \` Autocompletes\n\` - \` Tag System\n\` - \` Features all the plugins in [this repository](https://github.com/sern-handler/awesome-plugins)`,
|
||||
// },
|
||||
];
|
||||
return obj.map(({ name, value }) => `**${name}**\n${value}`).join("\n\n");
|
||||
}
|
||||
|
||||
@@ -1,134 +1,135 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
User,
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import { publish } from "#plugins";
|
||||
import { slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
description: "wanna win in rps?",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "user you wanna play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
execute: async (context) => {
|
||||
const opponent = context.interaction.options.getUser("user") ?? context.client.user!;
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
description: "wanna win in rps?",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "user you wanna play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
execute: async (context) => {
|
||||
const opponent = context.interaction.options.getUser("user") ?? context.client.user!;
|
||||
|
||||
if (opponent.id === context.user.id) return context.reply(`Can't play with yourself dumb dumb`);
|
||||
if (opponent.id === context.user.id)
|
||||
return context.reply(`Can't play with yourself dumb dumb`);
|
||||
|
||||
const buttons = ["🪨|Rock", "📄|Paper", "✂|Scissors"].map((s) => {
|
||||
const [emoji, label] = s.split("|");
|
||||
return new ButtonBuilder()
|
||||
.setCustomId(label.toLowerCase())
|
||||
.setEmoji(emoji)
|
||||
.setLabel(label)
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
});
|
||||
const buttons = ["🪨|Rock", "📄|Paper", "✂|Scissors"].map((s) => {
|
||||
const [emoji, label] = s.split("|");
|
||||
return new ButtonBuilder()
|
||||
.setCustomId(label.toLowerCase())
|
||||
.setEmoji(emoji)
|
||||
.setLabel(label)
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
});
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||
|
||||
let content = `${context.user} vs ${opponent}`;
|
||||
let content = `${context.user} vs ${opponent}`;
|
||||
|
||||
if (!opponent.bot) {
|
||||
content += `\n\n> Waiting for ${context.user.username}\n> Waiting for ${opponent.username}`;
|
||||
}
|
||||
if (!opponent.bot) {
|
||||
content += `\n\n> Waiting for ${context.user.username}\n> Waiting for ${opponent.username}`;
|
||||
}
|
||||
|
||||
const sent = await context.reply({
|
||||
content,
|
||||
components: [row.setComponents(buttons)],
|
||||
});
|
||||
const sent = await context.reply({
|
||||
content,
|
||||
components: [row.setComponents(buttons)],
|
||||
});
|
||||
|
||||
const collector = sent.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => [context.user.id, opponent.id].includes(i.user.id),
|
||||
time: 60_000,
|
||||
});
|
||||
const collector = sent.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => [context.user.id, opponent.id].includes(i.user.id),
|
||||
time: 60_000,
|
||||
});
|
||||
|
||||
collector.on("ignore", async (i) => {
|
||||
await i.reply({
|
||||
content: `Couldn't ignore you less`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
collector.on("ignore", async (i) => {
|
||||
await i.reply({
|
||||
content: `Couldn't ignore you less`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
let opponentChoice: Choice;
|
||||
let userChoice: Choice;
|
||||
let opponentChoice: Choice;
|
||||
let userChoice: Choice;
|
||||
|
||||
const getResponses = (i: ButtonInteraction) => {
|
||||
opponentChoice ??= (
|
||||
opponent.bot
|
||||
? ["rock", "paper", "scissors"][(3 * Math.random()) | 0]
|
||||
: i.user.id === opponent.id
|
||||
? i.customId
|
||||
: undefined
|
||||
) as Choice;
|
||||
const getResponses = (i: ButtonInteraction) => {
|
||||
opponentChoice ??= (
|
||||
opponent.bot
|
||||
? ["rock", "paper", "scissors"][(3 * Math.random()) | 0]
|
||||
: i.user.id === opponent.id
|
||||
? i.customId
|
||||
: undefined
|
||||
) as Choice;
|
||||
|
||||
userChoice ??= (i.user.id === context.user.id ? i.customId : undefined) as Choice;
|
||||
return [userChoice, opponentChoice];
|
||||
};
|
||||
userChoice ??= (i.user.id === context.user.id ? i.customId : undefined) as Choice;
|
||||
return [userChoice, opponentChoice];
|
||||
};
|
||||
|
||||
const computeResults = () => {
|
||||
content =
|
||||
content.split("\n")[0] +
|
||||
`\n\n> ${context.user.username} chose ${emoji[userChoice]}!` +
|
||||
`\n> ${opponent.username} chose ${emoji[opponentChoice]}!\n\nResults: `;
|
||||
const computeResults = () => {
|
||||
content =
|
||||
content.split("\n")[0] +
|
||||
`\n\n> ${context.user.username} chose ${emoji[userChoice]}!` +
|
||||
`\n> ${opponent.username} chose ${emoji[opponentChoice]}!\n\nResults: `;
|
||||
|
||||
const win = (user: User) => (content += `${user} wins! GG 🥳`);
|
||||
const win = (user: User) => (content += `${user} wins! GG 🥳`);
|
||||
|
||||
switch (`${userChoice}-${opponentChoice}` as Possibilities) {
|
||||
case "paper-rock":
|
||||
case "rock-scissors":
|
||||
case "scissors-paper":
|
||||
return win(context.user);
|
||||
case "paper-scissors":
|
||||
case "scissors-rock":
|
||||
case "rock-paper":
|
||||
return win(opponent);
|
||||
default:
|
||||
return (content += `oof! There was a tie!`);
|
||||
}
|
||||
};
|
||||
switch (`${userChoice}-${opponentChoice}` as Possibilities) {
|
||||
case "paper-rock":
|
||||
case "rock-scissors":
|
||||
case "scissors-paper":
|
||||
return win(context.user);
|
||||
case "paper-scissors":
|
||||
case "scissors-rock":
|
||||
case "rock-paper":
|
||||
return win(opponent);
|
||||
default:
|
||||
return (content += `oof! There was a tie!`);
|
||||
}
|
||||
};
|
||||
|
||||
buttons.forEach((b) => b.setDisabled());
|
||||
buttons.forEach((b) => b.setDisabled());
|
||||
|
||||
collector.on("collect", async (i) => {
|
||||
collector.resetTimer();
|
||||
const choices = getResponses(i).filter(Boolean);
|
||||
collector.on("collect", async (i) => {
|
||||
collector.resetTimer();
|
||||
const choices = getResponses(i).filter(Boolean);
|
||||
|
||||
if (!opponent.bot && choices.length !== 2) {
|
||||
content = content.replace(
|
||||
`> Waiting for ${i.user.username}`,
|
||||
`> ${i.user.username} has chosen!`
|
||||
);
|
||||
return void i.update(content);
|
||||
}
|
||||
collector.stop("finished");
|
||||
await i.update({
|
||||
content: computeResults(),
|
||||
components: [row.setComponents(buttons)],
|
||||
});
|
||||
});
|
||||
if (!opponent.bot && choices.length !== 2) {
|
||||
content = content.replace(
|
||||
`> Waiting for ${i.user.username}`,
|
||||
`> ${i.user.username} has chosen!`,
|
||||
);
|
||||
return void i.update(content);
|
||||
}
|
||||
collector.stop("finished");
|
||||
await i.update({
|
||||
content: computeResults(),
|
||||
components: [row.setComponents(buttons)],
|
||||
});
|
||||
});
|
||||
|
||||
collector.on("end", async (_, r) => {
|
||||
if (r === "finished") return;
|
||||
await context.interaction
|
||||
.editReply({
|
||||
content: "Time up!",
|
||||
components: [row.setComponents(buttons)],
|
||||
})
|
||||
.catch(() => null);
|
||||
});
|
||||
},
|
||||
collector.on("end", async (_, r) => {
|
||||
if (r === "finished") return;
|
||||
await context.interaction
|
||||
.editReply({
|
||||
content: "Time up!",
|
||||
components: [row.setComponents(buttons)],
|
||||
})
|
||||
.catch(() => null);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
type Choice = "rock" | "paper" | "scissors";
|
||||
@@ -136,7 +137,7 @@ type Choice = "rock" | "paper" | "scissors";
|
||||
type Possibilities = `${Choice}-${Choice}`;
|
||||
|
||||
const emoji: Record<Choice, string> = {
|
||||
rock: "🪨",
|
||||
paper: "📄",
|
||||
scissors: "✂",
|
||||
rock: "🪨",
|
||||
paper: "📄",
|
||||
scissors: "✂",
|
||||
};
|
||||
|
||||
@@ -3,39 +3,39 @@ import { publish } from "#plugins";
|
||||
import { slashCommand, Timestamp, TicTacToe } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
options: [
|
||||
{
|
||||
name: "opponent",
|
||||
description: "Opponent you would like to play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
description: "Lets play a game of tic tac toe!",
|
||||
async execute(ctx) {
|
||||
const { interaction } = ctx;
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
options: [
|
||||
{
|
||||
name: "opponent",
|
||||
description: "Opponent you would like to play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
description: "Lets play a game of tic tac toe!",
|
||||
async execute(ctx) {
|
||||
const { interaction } = ctx;
|
||||
|
||||
const game = new TicTacToe();
|
||||
const user = game.sanityChecks(interaction);
|
||||
if (!user) return;
|
||||
const game = new TicTacToe();
|
||||
const user = game.sanityChecks(interaction);
|
||||
if (!user) return;
|
||||
|
||||
let pieces = game.buildRows();
|
||||
let pieces = game.buildRows();
|
||||
|
||||
let content =
|
||||
`Let the game begin!\n${interaction.user} vs ${user}\n\n> Current Chance: ${interaction.user} [X]` +
|
||||
`\nTime ends ${new Timestamp(Date.now() + 60_000).getRelativeTime()}`;
|
||||
let content =
|
||||
`Let the game begin!\n${interaction.user} vs ${user}\n\n> Current Chance: ${interaction.user} [X]` +
|
||||
`\nTime ends ${new Timestamp(Date.now() + 60_000).getRelativeTime()}`;
|
||||
|
||||
const sent = await interaction.reply({
|
||||
content,
|
||||
components: pieces,
|
||||
});
|
||||
const sent = await interaction.reply({
|
||||
content,
|
||||
components: pieces,
|
||||
});
|
||||
|
||||
const collector = game.createCollector(sent, interaction.user, user);
|
||||
const collector = game.createCollector(sent, interaction.user, user);
|
||||
|
||||
let chance = interaction.user;
|
||||
let mark: "X" | "O" = "X";
|
||||
let chance = interaction.user;
|
||||
let mark: "X" | "O" = "X";
|
||||
|
||||
game.HumanGame(interaction, collector, chance, mark, content, pieces, user);
|
||||
},
|
||||
game.HumanGame(interaction, collector, chance, mark, content, pieces, user);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,203 +4,207 @@ import { ApplicationCommandOptionType } from "discord.js";
|
||||
import { Timestamp } from "#utils";
|
||||
import { Emojis } from "#constants";
|
||||
const prefix = (t: unknown) => (t ? "$" : "#");
|
||||
const octokit = Service('octokit');
|
||||
const octokit = Service("octokit");
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "Get info about a PR or issue",
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
options: [
|
||||
{
|
||||
name: "repo",
|
||||
description: "The repo to get info from",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
const text = ctx.options.getFocused();
|
||||
const org = await octokit.repos.listForOrg({
|
||||
org: "sern-handler",
|
||||
});
|
||||
type: CommandType.Slash,
|
||||
description: "Get info about a PR or issue",
|
||||
plugins: [publish({ dmPermission: false })],
|
||||
options: [
|
||||
{
|
||||
name: "repo",
|
||||
description: "The repo to get info from",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
const text = ctx.options.getFocused();
|
||||
const org = await octokit.repos.listForOrg({
|
||||
org: "sern-handler",
|
||||
});
|
||||
|
||||
if (!org) return ctx.respond([]);
|
||||
if (!org) return ctx.respond([]);
|
||||
|
||||
const topRepos = org.data.sort(
|
||||
(a, b) => (b.stargazers_count ?? 0) - (a.stargazers_count ?? 0)
|
||||
);
|
||||
const topRepos = org.data.sort(
|
||||
(a, b) => (b.stargazers_count ?? 0) - (a.stargazers_count ?? 0),
|
||||
);
|
||||
|
||||
const publicRepos = topRepos
|
||||
.filter((r) => !r.private)
|
||||
.map((repo) => ({
|
||||
name: `sern/${repo.name}`,
|
||||
value: repo.name,
|
||||
}));
|
||||
const publicRepos = topRepos
|
||||
.filter((r) => !r.private)
|
||||
.map((repo) => ({
|
||||
name: `sern/${repo.name}`,
|
||||
value: repo.name,
|
||||
}));
|
||||
|
||||
if (!text.length) {
|
||||
return ctx.respond(publicRepos.slice(0, 25)).catch(() => null);
|
||||
}
|
||||
return ctx
|
||||
.respond(
|
||||
publicRepos
|
||||
.filter((repo) => repo.name.toLowerCase().includes(text.toLowerCase()))
|
||||
.slice(0, 25)
|
||||
)
|
||||
.catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "number",
|
||||
description: "The number of the PR or issue",
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
if (!text.length) {
|
||||
return ctx.respond(publicRepos.slice(0, 25)).catch(() => null);
|
||||
}
|
||||
return ctx
|
||||
.respond(
|
||||
publicRepos
|
||||
.filter((repo) =>
|
||||
repo.name.toLowerCase().includes(text.toLowerCase()),
|
||||
)
|
||||
.slice(0, 25),
|
||||
)
|
||||
.catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "number",
|
||||
description: "The number of the PR or issue",
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
const text = ctx.options.getFocused();
|
||||
const repo = ctx.options.getString("repo");
|
||||
if (!repo) return ctx.respond([]);
|
||||
|
||||
const text = ctx.options.getFocused();
|
||||
const repo = ctx.options.getString("repo");
|
||||
if (!repo) return ctx.respond([]);
|
||||
let search;
|
||||
|
||||
let search;
|
||||
if (text.length) {
|
||||
search = await octokit.search
|
||||
.issuesAndPullRequests({
|
||||
q: `repo:sern-handler/${repo} ${text} in:title`,
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
if (text.length) {
|
||||
search = await octokit.search
|
||||
.issuesAndPullRequests({
|
||||
q: `repo:sern-handler/${repo} ${text} in:title`,
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
if (!text.length) {
|
||||
const issues = await octokit.issues
|
||||
.listForRepo({
|
||||
owner: "sern-handler",
|
||||
repo,
|
||||
state: "all",
|
||||
per_page: 25,
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
if (!text.length) {
|
||||
const issues = await octokit.issues
|
||||
.listForRepo({
|
||||
owner: "sern-handler",
|
||||
repo,
|
||||
state: "all",
|
||||
per_page: 25,
|
||||
})
|
||||
.catch(() => null);
|
||||
if (!issues) return ctx.respond([]);
|
||||
|
||||
if (!issues) return ctx.respond([]);
|
||||
const map = issues.data.map((issue) => ({
|
||||
name: cutText(
|
||||
`${prefix(issue.pull_request)}${issue.number} - ${issue.title}`,
|
||||
),
|
||||
value: issue.number,
|
||||
}));
|
||||
|
||||
const map = issues.data.map((issue) => ({
|
||||
name: cutText(`${prefix(issue.pull_request)}${issue.number} - ${issue.title}`),
|
||||
value: issue.number,
|
||||
}));
|
||||
return ctx.respond(map).catch(() => null);
|
||||
}
|
||||
|
||||
return ctx.respond(map).catch(() => null);
|
||||
}
|
||||
return ctx
|
||||
.respond(
|
||||
search?.data.items
|
||||
.filter((i) => i.title.toLowerCase().includes(text.toLowerCase()))
|
||||
.map((issue) => ({
|
||||
name: cutText(
|
||||
`${prefix(issue.pull_request)}${issue.number} - ${issue.title}`,
|
||||
),
|
||||
value: issue.number,
|
||||
}))
|
||||
.slice(0, 25) ?? [],
|
||||
)
|
||||
.catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
description: "Who should I ping that should see this?",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const repo = options.getString("repo", true);
|
||||
const number = options.getInteger("number", true);
|
||||
const target = options.getUser("target");
|
||||
|
||||
return ctx
|
||||
.respond(
|
||||
search?.data.items
|
||||
.filter((i) => i.title.toLowerCase().includes(text.toLowerCase()))
|
||||
.map((issue) => ({
|
||||
name: cutText(`${prefix(issue.pull_request)}${issue.number} - ${issue.title}`),
|
||||
value: issue.number,
|
||||
}))
|
||||
.slice(0, 25) ?? []
|
||||
)
|
||||
.catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
description: "Who should I ping that should see this?",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const issue = await octokit.issues
|
||||
.get({
|
||||
owner: "sern-handler",
|
||||
repo,
|
||||
issue_number: number,
|
||||
})
|
||||
.then((r) => r.data)
|
||||
.catch(() => null);
|
||||
|
||||
const repo = options.getString("repo", true);
|
||||
const number = options.getInteger("number", true);
|
||||
const target = options.getUser("target");
|
||||
if (!issue) {
|
||||
return ctx.reply({
|
||||
content: `I could not find [\`#${number} in sern/${repo}\`](https://github.com/sern-handler/${repo}/)`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
const emoji = (i: typeof issue): string => {
|
||||
if (i.pull_request) {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
return i.draft ? Emojis.PRDraft : Emojis.PROpen;
|
||||
case "closed":
|
||||
return i.pull_request.merged_at ? Emojis.PRMerged : Emojis.PRClosed;
|
||||
}
|
||||
}
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
return Emojis.IssueOpen;
|
||||
case "closed":
|
||||
return i.state_reason === "completed"
|
||||
? Emojis.IssueClosed
|
||||
: i.state_reason === "not_planned"
|
||||
? Emojis.IssueNotPlanned
|
||||
: "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const issue = await octokit.issues
|
||||
.get({
|
||||
owner: "sern-handler",
|
||||
repo,
|
||||
issue_number: number,
|
||||
})
|
||||
.then((r) => r.data)
|
||||
.catch(() => null);
|
||||
const suffix = (i: typeof issue): string => {
|
||||
let str = "";
|
||||
let time = "";
|
||||
if (i.pull_request) {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
str = i.draft ? "drafted" : "opened";
|
||||
time = i.created_at;
|
||||
break;
|
||||
case "closed":
|
||||
str = i.pull_request.merged_at ? "merged" : "closed";
|
||||
time = i.pull_request.merged_at ?? i.closed_at ?? "";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
str = "opened";
|
||||
time = i.created_at;
|
||||
break;
|
||||
case "closed":
|
||||
str = i.state_reason === "completed" ? "completed" : "closed";
|
||||
time = i.closed_at ?? "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${str} ${new Timestamp(new Date(time).getTime()).getRelativeTime()}`;
|
||||
};
|
||||
|
||||
if (!issue) {
|
||||
return ctx.reply({
|
||||
content: `I could not find [\`#${number} in sern/${repo}\`](https://github.com/sern-handler/${repo}/)`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
const emoji = (i: typeof issue): string => {
|
||||
if (i.pull_request) {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
return i.draft ? Emojis.PRDraft : Emojis.PROpen;
|
||||
case "closed":
|
||||
return i.pull_request.merged_at ? Emojis.PRMerged : Emojis.PRClosed;
|
||||
}
|
||||
}
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
return Emojis.IssueOpen;
|
||||
case "closed":
|
||||
return i.state_reason === "completed"
|
||||
? Emojis.IssueClosed
|
||||
: i.state_reason === "not_planned"
|
||||
? Emojis.IssueNotPlanned
|
||||
: "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
let reply = target
|
||||
? `*GitHub ${issue.pull_request ? "Pull Request" : "Issue"} data for ${target}*\n`
|
||||
: "";
|
||||
|
||||
const suffix = (i: typeof issue): string => {
|
||||
let str = "";
|
||||
let time = "";
|
||||
if (i.pull_request) {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
str = i.draft ? "drafted" : "opened";
|
||||
time = i.created_at;
|
||||
break;
|
||||
case "closed":
|
||||
str = i.pull_request.merged_at ? "merged" : "closed";
|
||||
time = i.pull_request.merged_at ?? i.closed_at ?? "";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (i.state) {
|
||||
case "open":
|
||||
str = "opened";
|
||||
time = i.created_at;
|
||||
break;
|
||||
case "closed":
|
||||
str = i.state_reason === "completed" ? "completed" : "closed";
|
||||
time = i.closed_at ?? "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${str} ${new Timestamp(new Date(time).getTime()).getRelativeTime()}`;
|
||||
};
|
||||
reply += `${emoji(issue)} [\`${prefix(issue.pull_request)}${number} sern/${repo}\`](<${
|
||||
issue.html_url
|
||||
}>)\n___${issue.title}___ by [*${issue.user!.login}*](<${issue.user?.html_url}>) ${suffix(
|
||||
issue,
|
||||
)}`;
|
||||
|
||||
let reply = target
|
||||
? `*GitHub ${issue.pull_request ? "Pull Request" : "Issue"} data for ${target}*\n`
|
||||
: "";
|
||||
|
||||
reply += `${emoji(issue)} [\`${prefix(issue.pull_request)}${number} sern/${repo}\`](<${
|
||||
issue.html_url
|
||||
}>)\n___${issue.title}___ by [*${issue.user!.login}*](<${issue.user?.html_url}>) ${suffix(
|
||||
issue
|
||||
)}`;
|
||||
|
||||
return ctx.reply(reply);
|
||||
},
|
||||
return ctx.reply(reply);
|
||||
},
|
||||
});
|
||||
|
||||
function cutText(text: string) {
|
||||
return text.length > 99 ? text.slice(0, 97) + "..." : text;
|
||||
return text.length > 99 ? text.slice(0, 97) + "..." : text;
|
||||
}
|
||||
|
||||
@@ -3,32 +3,32 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
import { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Button,
|
||||
name: "emoji/accept",
|
||||
async execute(ctx) {
|
||||
if (!ownerIDs.includes(ctx.user.id))
|
||||
return ctx.reply({
|
||||
ephemeral: true,
|
||||
content: `You really thought the devs would allow you to accept the emoji?`,
|
||||
});
|
||||
type: CommandType.Button,
|
||||
name: "emoji/accept",
|
||||
async execute(ctx) {
|
||||
if (!ownerIDs.includes(ctx.user.id))
|
||||
return ctx.reply({
|
||||
ephemeral: true,
|
||||
content: `You really thought the devs would allow you to accept the emoji?`,
|
||||
});
|
||||
|
||||
const suggestedName = ctx.message.embeds[0].fields[0].value!;
|
||||
const suggestedName = ctx.message.embeds[0].fields[0].value!;
|
||||
|
||||
const modal = new ModalBuilder().setCustomId("emojiModal").setTitle("Emoji Creation");
|
||||
const modal = new ModalBuilder().setCustomId("emojiModal").setTitle("Emoji Creation");
|
||||
|
||||
const row = new ActionRowBuilder<TextInputBuilder>().setComponents(
|
||||
new TextInputBuilder() //
|
||||
.setCustomId("emoji/name")
|
||||
.setLabel("Emoji Name")
|
||||
.setMinLength(2)
|
||||
.setMaxLength(32)
|
||||
.setPlaceholder("Name of the emoji")
|
||||
.setRequired()
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(suggestedName)
|
||||
);
|
||||
modal.setComponents(row);
|
||||
const row = new ActionRowBuilder<TextInputBuilder>().setComponents(
|
||||
new TextInputBuilder() //
|
||||
.setCustomId("emoji/name")
|
||||
.setLabel("Emoji Name")
|
||||
.setMinLength(2)
|
||||
.setMaxLength(32)
|
||||
.setPlaceholder("Name of the emoji")
|
||||
.setRequired()
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(suggestedName),
|
||||
);
|
||||
modal.setComponents(row);
|
||||
|
||||
await ctx.showModal(modal);
|
||||
},
|
||||
await ctx.showModal(modal);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,37 +3,40 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
import { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Button,
|
||||
name: "emoji/deny",
|
||||
async execute(ctx) {
|
||||
if (!ownerIDs.includes(ctx.user.id))
|
||||
return ctx.reply({
|
||||
ephemeral: true,
|
||||
content: `You really thought the devs would allow you to deny the emoji?`,
|
||||
});
|
||||
type: CommandType.Button,
|
||||
name: "emoji/deny",
|
||||
async execute(ctx) {
|
||||
if (!ownerIDs.includes(ctx.user.id))
|
||||
return ctx.reply({
|
||||
ephemeral: true,
|
||||
content: `You really thought the devs would allow you to deny the emoji?`,
|
||||
});
|
||||
|
||||
await ctx.deferUpdate();
|
||||
await ctx.deferUpdate();
|
||||
|
||||
const components = [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
ctx.message!.components[0].components.map((c) => new ButtonBuilder(c.data).setDisabled())
|
||||
),
|
||||
];
|
||||
const components = [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
ctx.message!.components[0].components.map((c) =>
|
||||
new ButtonBuilder(c.data).setDisabled(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) //
|
||||
.setFields(ctx.message!.embeds[0].fields[0], {
|
||||
name: "Status",
|
||||
value:
|
||||
`Denied by ${ctx.user}` + "\nThank you but we are not interested in this at the moment!",
|
||||
})
|
||||
.setColor("Red")
|
||||
.setTimestamp();
|
||||
const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) //
|
||||
.setFields(ctx.message!.embeds[0].fields[0], {
|
||||
name: "Status",
|
||||
value:
|
||||
`Denied by ${ctx.user}` +
|
||||
"\nThank you but we are not interested in this at the moment!",
|
||||
})
|
||||
.setColor("Red")
|
||||
.setTimestamp();
|
||||
|
||||
await ctx.message?.edit({
|
||||
content: null,
|
||||
embeds: [embed],
|
||||
components,
|
||||
files: [],
|
||||
});
|
||||
},
|
||||
await ctx.message?.edit({
|
||||
content: null,
|
||||
embeds: [embed],
|
||||
components,
|
||||
files: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,64 +2,66 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
import { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Modal,
|
||||
async execute(ctx) {
|
||||
const emojiURL = ctx.message!.embeds[0].image?.url;
|
||||
if (!emojiURL)
|
||||
return ctx.reply({
|
||||
content: "Uh something bad happened, I couldn't get the emoji!",
|
||||
ephemeral: true,
|
||||
});
|
||||
const name = ctx.fields.getTextInputValue("emoji/name");
|
||||
type: CommandType.Modal,
|
||||
async execute(ctx) {
|
||||
const emojiURL = ctx.message!.embeds[0].image?.url;
|
||||
if (!emojiURL)
|
||||
return ctx.reply({
|
||||
content: "Uh something bad happened, I couldn't get the emoji!",
|
||||
ephemeral: true,
|
||||
});
|
||||
const name = ctx.fields.getTextInputValue("emoji/name");
|
||||
|
||||
const emoji = await ctx.guild?.emojis
|
||||
.create({
|
||||
attachment: emojiURL,
|
||||
name,
|
||||
reason: `Accepted by ${ctx.user.tag}`,
|
||||
})
|
||||
.catch((e) => {
|
||||
ctx.reply({
|
||||
content: e.message,
|
||||
ephemeral: true,
|
||||
});
|
||||
return null;
|
||||
});
|
||||
const emoji = await ctx.guild?.emojis
|
||||
.create({
|
||||
attachment: emojiURL,
|
||||
name,
|
||||
reason: `Accepted by ${ctx.user.tag}`,
|
||||
})
|
||||
.catch((e) => {
|
||||
ctx.reply({
|
||||
content: e.message,
|
||||
ephemeral: true,
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!emoji) return;
|
||||
if (!emoji) return;
|
||||
|
||||
await ctx.deferUpdate();
|
||||
await ctx.deferUpdate();
|
||||
|
||||
const components = [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
ctx.message!.components[0].components.map((c) => new ButtonBuilder(c.data).setDisabled())
|
||||
),
|
||||
];
|
||||
const components = [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
ctx.message!.components[0].components.map((c) =>
|
||||
new ButtonBuilder(c.data).setDisabled(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) //
|
||||
.setFields(
|
||||
{
|
||||
name: "Emoji details",
|
||||
value: `\` - \` Name: ${emoji?.name}\n\` - \` Animated: ${
|
||||
emoji?.animated ? "Yes" : "Nope"
|
||||
}\n\` - \` Emoji: ${emoji}\n\` - \` Raw: \`${emoji.toString()}\``,
|
||||
},
|
||||
{
|
||||
name: "Status",
|
||||
value: `Accepted by ${ctx.user}`,
|
||||
}
|
||||
)
|
||||
.setColor("Green")
|
||||
.setFooter({
|
||||
text: `GG ${ctx.message?.embeds[0].author?.name}`,
|
||||
})
|
||||
.setTimestamp();
|
||||
const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) //
|
||||
.setFields(
|
||||
{
|
||||
name: "Emoji details",
|
||||
value: `\` - \` Name: ${emoji?.name}\n\` - \` Animated: ${
|
||||
emoji?.animated ? "Yes" : "Nope"
|
||||
}\n\` - \` Emoji: ${emoji}\n\` - \` Raw: \`${emoji.toString()}\``,
|
||||
},
|
||||
{
|
||||
name: "Status",
|
||||
value: `Accepted by ${ctx.user}`,
|
||||
},
|
||||
)
|
||||
.setColor("Green")
|
||||
.setFooter({
|
||||
text: `GG ${ctx.message?.embeds[0].author?.name}`,
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
await ctx.message?.edit({
|
||||
content: null,
|
||||
embeds: [embed],
|
||||
components,
|
||||
files: [],
|
||||
});
|
||||
},
|
||||
await ctx.message?.edit({
|
||||
content: null,
|
||||
embeds: [embed],
|
||||
components,
|
||||
files: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,27 +2,27 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
import type { TagMessage } from "typings";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Button,
|
||||
name: "@falseTrigger",
|
||||
description: "False Trigger of tag",
|
||||
async execute(ctx) {
|
||||
const { message }: { message: TagMessage } = ctx;
|
||||
if (!message.tagTriggerId) {
|
||||
await ctx.reply({
|
||||
content: "Sorry, this interaction is expired",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
type: CommandType.Button,
|
||||
name: "@falseTrigger",
|
||||
description: "False Trigger of tag",
|
||||
async execute(ctx) {
|
||||
const { message }: { message: TagMessage } = ctx;
|
||||
if (!message.tagTriggerId) {
|
||||
await ctx.reply({
|
||||
content: "Sorry, this interaction is expired",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.tagTriggerId !== ctx.user.id) {
|
||||
await ctx.reply({
|
||||
content: "This ain't your shit",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await ctx.deferUpdate();
|
||||
if (message.tagTriggerId !== ctx.user.id) {
|
||||
await ctx.reply({
|
||||
content: "This ain't your shit",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await ctx.deferUpdate();
|
||||
|
||||
message.deletable && (await message.delete().catch(() => null));
|
||||
},
|
||||
message.deletable && (await message.delete().catch(() => null));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,32 +2,33 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
import type { GuildMember, APIStringSelectComponent } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.StringSelect,
|
||||
name: "role-menu",
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
type: CommandType.StringSelect,
|
||||
name: "role-menu",
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const roles = interaction.values;
|
||||
const roles = interaction.values;
|
||||
|
||||
const menuRoles: string[] = (
|
||||
interaction.message.components[0].components[0].data as Readonly<APIStringSelectComponent>
|
||||
).options.map((o: { label: string; value: string }) => o.value);
|
||||
const menuRoles: string[] = (
|
||||
interaction.message.components[0].components[0]
|
||||
.data as Readonly<APIStringSelectComponent>
|
||||
).options.map((o: { label: string; value: string }) => o.value);
|
||||
|
||||
const member = interaction.member as GuildMember;
|
||||
if (!member) return;
|
||||
const member = interaction.member as GuildMember;
|
||||
if (!member) return;
|
||||
|
||||
let content = `Roles Updated, you have been given the following roles:\n${roles
|
||||
.map((r) => `<@&${r}>`)
|
||||
.join("\n")}`;
|
||||
if (roles.length === 0) content = "No roles were selected, updated roles";
|
||||
let content = `Roles Updated, you have been given the following roles:\n${roles
|
||||
.map((r) => `<@&${r}>`)
|
||||
.join("\n")}`;
|
||||
if (roles.length === 0) content = "No roles were selected, updated roles";
|
||||
|
||||
const existing = member.roles.cache
|
||||
.filter((r) => r.id !== interaction.guildId)
|
||||
.map((r) => r.id)
|
||||
.filter((r) => !menuRoles.includes(r));
|
||||
const existing = member.roles.cache
|
||||
.filter((r) => r.id !== interaction.guildId)
|
||||
.map((r) => r.id)
|
||||
.filter((r) => !menuRoles.includes(r));
|
||||
|
||||
await member.roles.set(roles.concat(existing)).catch(() => null);
|
||||
await member.roles.set(roles.concat(existing)).catch(() => null);
|
||||
|
||||
await interaction.editReply(content);
|
||||
},
|
||||
await interaction.editReply(content);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,52 +6,54 @@ import { TagList } from "#constants";
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Modal,
|
||||
name: "@sern/tag/create",
|
||||
description: "Creation of tag",
|
||||
execute(ctx) {
|
||||
const tagName = ctx.fields.getTextInputValue("tag-name");
|
||||
const tagContent = ctx.fields.getTextInputValue("tag-content");
|
||||
const keywords = ctx.fields.getTextInputValue("tag-keywords");
|
||||
const tag: TagData = {
|
||||
name: tagName,
|
||||
content: tagContent,
|
||||
keywords: keywords
|
||||
? [
|
||||
...new Set(
|
||||
keywords
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((c) => c.trim())
|
||||
.filter((c) => !!c.length)
|
||||
),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
const filePath = `./tags.json`;
|
||||
if (!existsSync(filePath)) {
|
||||
const tags = [tag];
|
||||
writeFileSync(filePath, JSON.stringify(tags, null, 2));
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
type: CommandType.Modal,
|
||||
name: "@sern/tag/create",
|
||||
description: "Creation of tag",
|
||||
execute(ctx) {
|
||||
const tagName = ctx.fields.getTextInputValue("tag-name");
|
||||
const tagContent = ctx.fields.getTextInputValue("tag-content");
|
||||
const keywords = ctx.fields.getTextInputValue("tag-keywords");
|
||||
const tag: TagData = {
|
||||
name: tagName,
|
||||
content: tagContent,
|
||||
keywords: keywords
|
||||
? [
|
||||
...new Set(
|
||||
keywords
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((c) => c.trim())
|
||||
.filter((c) => !!c.length),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
const filePath = `./tags.json`;
|
||||
if (!existsSync(filePath)) {
|
||||
const tags = [tag];
|
||||
writeFileSync(filePath, JSON.stringify(tags, null, 2));
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
|
||||
if (file.find((t) => t.name === tagName)) {
|
||||
return ctx.reply(`Tag __${tagName}__ already exists`);
|
||||
}
|
||||
if (file.find((t) => t.name === tagName)) {
|
||||
return ctx.reply(`Tag __${tagName}__ already exists`);
|
||||
}
|
||||
|
||||
const similarKeywords = file.filter((t) => t.keywords.some((k) => tag.keywords.includes(k)));
|
||||
if (similarKeywords.length) {
|
||||
return ctx.reply(
|
||||
`Tag __${tagName}__ has similar keywords to __${similarKeywords
|
||||
.map((t) => t.name)
|
||||
.join(", ")}__`
|
||||
);
|
||||
}
|
||||
file.push(tag);
|
||||
writeFileSync(filePath, JSON.stringify(file, null, 2));
|
||||
}
|
||||
return ctx.reply({
|
||||
content: `Tag __${tagName}__ created`,
|
||||
});
|
||||
},
|
||||
const similarKeywords = file.filter((t) =>
|
||||
t.keywords.some((k) => tag.keywords.includes(k)),
|
||||
);
|
||||
if (similarKeywords.length) {
|
||||
return ctx.reply(
|
||||
`Tag __${tagName}__ has similar keywords to __${similarKeywords
|
||||
.map((t) => t.name)
|
||||
.join(", ")}__`,
|
||||
);
|
||||
}
|
||||
file.push(tag);
|
||||
writeFileSync(filePath, JSON.stringify(file, null, 2));
|
||||
}
|
||||
return ctx.reply({
|
||||
content: `Tag __${tagName}__ created`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,54 +6,54 @@ import type { TagData } from "typings";
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Modal,
|
||||
name: "@sern/tag/edit",
|
||||
description: "Edition of tag",
|
||||
async execute(ctx) {
|
||||
const tagName = ctx.fields.getTextInputValue("tag-name");
|
||||
const tagContent = ctx.fields.getTextInputValue("tag-content");
|
||||
const keywords = ctx.fields.getTextInputValue("tag-keywords");
|
||||
type: CommandType.Modal,
|
||||
name: "@sern/tag/edit",
|
||||
description: "Edition of tag",
|
||||
async execute(ctx) {
|
||||
const tagName = ctx.fields.getTextInputValue("tag-name");
|
||||
const tagContent = ctx.fields.getTextInputValue("tag-content");
|
||||
const keywords = ctx.fields.getTextInputValue("tag-keywords");
|
||||
|
||||
const tag: TagData = {
|
||||
name: tagName,
|
||||
content: tagContent,
|
||||
keywords: keywords
|
||||
? keywords
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((c) => c.trim())
|
||||
.filter((c) => !!c.length)
|
||||
: [],
|
||||
};
|
||||
const filePath = `./tags.json`;
|
||||
const file: TagData[] = require(TagList);
|
||||
const oldTag = file.find((t) => t.name === (ctx.user.data as { tag: string }).tag)!;
|
||||
const tag: TagData = {
|
||||
name: tagName,
|
||||
content: tagContent,
|
||||
keywords: keywords
|
||||
? keywords
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((c) => c.trim())
|
||||
.filter((c) => !!c.length)
|
||||
: [],
|
||||
};
|
||||
const filePath = `./tags.json`;
|
||||
const file: TagData[] = require(TagList);
|
||||
const oldTag = file.find((t) => t.name === (ctx.user.data as { tag: string }).tag)!;
|
||||
|
||||
const similarKeywords = file.filter(
|
||||
(t) =>
|
||||
t.keywords.some((k) => tag.keywords.includes(k)) &&
|
||||
t.name !== oldTag.name &&
|
||||
t.content !== oldTag.content
|
||||
);
|
||||
if (similarKeywords.length) {
|
||||
return ctx.reply(
|
||||
`Tag __${tagName}__ has similar keywords to __${similarKeywords
|
||||
.map((t) => t.name)
|
||||
.join(", ")}__`
|
||||
);
|
||||
}
|
||||
const similarKeywords = file.filter(
|
||||
(t) =>
|
||||
t.keywords.some((k) => tag.keywords.includes(k)) &&
|
||||
t.name !== oldTag.name &&
|
||||
t.content !== oldTag.content,
|
||||
);
|
||||
if (similarKeywords.length) {
|
||||
return ctx.reply(
|
||||
`Tag __${tagName}__ has similar keywords to __${similarKeywords
|
||||
.map((t) => t.name)
|
||||
.join(", ")}__`,
|
||||
);
|
||||
}
|
||||
|
||||
file[file.findIndex((t) => t.name === (ctx.user.data as UserTag).tag)] = tag;
|
||||
file[file.findIndex((t) => t.name === (ctx.user.data as UserTag).tag)] = tag;
|
||||
|
||||
writeFileSync(filePath, JSON.stringify(file, null, 2));
|
||||
writeFileSync(filePath, JSON.stringify(file, null, 2));
|
||||
|
||||
return ctx.reply({
|
||||
content: `Tag __${tagName}__ edited!`,
|
||||
ephemeral: false,
|
||||
});
|
||||
},
|
||||
return ctx.reply({
|
||||
content: `Tag __${tagName}__ edited!`,
|
||||
ephemeral: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
interface UserTag {
|
||||
tag: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
@@ -7,28 +7,28 @@ import * as fs from "fs";
|
||||
import { AttachmentBuilder } from "discord.js";
|
||||
|
||||
async function ephemeral(ctx: Context, options: ReplyOptions) {
|
||||
const resolvedOptions = typeof options == "string" ? { content: options } : options;
|
||||
await ctx.interaction.editReply({ ...resolvedOptions });
|
||||
const resolvedOptions = typeof options == "string" ? { content: options } : options;
|
||||
await ctx.interaction.editReply({ ...resolvedOptions });
|
||||
}
|
||||
|
||||
export default slashCommand({
|
||||
description: "Fetch logs",
|
||||
plugins: [publish(), ownerOnly()],
|
||||
execute: async (ctx, args) => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const logPath = path.join(process.cwd(), "error.log");
|
||||
const readHandle = readFile(logPath, { signal: controller.signal });
|
||||
await ctx.interaction.deferReply({ ephemeral: true });
|
||||
if (!fs.existsSync(logPath)) {
|
||||
controller.abort();
|
||||
}
|
||||
const log = await readHandle;
|
||||
await ephemeral(ctx, {
|
||||
files: [new AttachmentBuilder(log).setName("error.log")],
|
||||
});
|
||||
} catch (e) {
|
||||
await ephemeral(ctx, "Couldn't find log. In dev mode?");
|
||||
}
|
||||
},
|
||||
description: "Fetch logs",
|
||||
plugins: [publish(), ownerOnly()],
|
||||
execute: async (ctx, args) => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const logPath = path.join(process.cwd(), "error.log");
|
||||
const readHandle = readFile(logPath, { signal: controller.signal });
|
||||
await ctx.interaction.deferReply({ ephemeral: true });
|
||||
if (!fs.existsSync(logPath)) {
|
||||
controller.abort();
|
||||
}
|
||||
const log = await readHandle;
|
||||
await ephemeral(ctx, {
|
||||
files: [new AttachmentBuilder(log).setName("error.log")],
|
||||
});
|
||||
} catch (e) {
|
||||
await ephemeral(ctx, "Couldn't find log. In dev mode?");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
Collection,
|
||||
Role,
|
||||
TextChannel,
|
||||
StringSelectMenuBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
Collection,
|
||||
Role,
|
||||
TextChannel,
|
||||
StringSelectMenuBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
} from "discord.js";
|
||||
import { ownerOnly, publish } from "#plugins";
|
||||
import { Resolver, slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [ownerOnly(), publish()],
|
||||
description: "Select Menu Role",
|
||||
options: [
|
||||
{
|
||||
name: "channel",
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
description: "The channel to send the message to",
|
||||
channelTypes: [ChannelType.GuildText],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "role",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "The roles to attach (upto 25)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "The message to send",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const channel = options.getChannel("channel", true) as TextChannel;
|
||||
const role = new Resolver(options.getString("role", true), ctx.interaction).roles;
|
||||
const message = options.getString("message", true);
|
||||
plugins: [ownerOnly(), publish()],
|
||||
description: "Select Menu Role",
|
||||
options: [
|
||||
{
|
||||
name: "channel",
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
description: "The channel to send the message to",
|
||||
channelTypes: [ChannelType.GuildText],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "role",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "The roles to attach (upto 25)",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "The message to send",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const channel = options.getChannel("channel", true) as TextChannel;
|
||||
const role = new Resolver(options.getString("role", true), ctx.interaction).roles;
|
||||
const message = options.getString("message", true);
|
||||
|
||||
if (role.size > 25) return ctx.reply("Too many roles");
|
||||
if (role.size > 25) return ctx.reply("Too many roles");
|
||||
|
||||
const cdn = role.filter(
|
||||
(r) => r.managed || r.position > (ctx.guild?.members.me)!.roles.highest.position
|
||||
).size;
|
||||
if (cdn) {
|
||||
return ctx.reply(
|
||||
`Some roles are managed by integration or higher than my highest role.\nPlease try again`
|
||||
);
|
||||
}
|
||||
await ctx.interaction.deferReply();
|
||||
const row = createMenu(channel, role);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(message)
|
||||
.setDescription(`Please select your roles below\nYou can select multiple roles`)
|
||||
.setColor(0xcc5279);
|
||||
await channel.send({
|
||||
embeds: [embed],
|
||||
components: [row],
|
||||
});
|
||||
await ctx.interaction.editReply("✅ Done!");
|
||||
},
|
||||
const cdn = role.filter(
|
||||
(r) => r.managed || r.position > (ctx.guild?.members.me)!.roles.highest.position,
|
||||
).size;
|
||||
if (cdn) {
|
||||
return ctx.reply(
|
||||
`Some roles are managed by integration or higher than my highest role.\nPlease try again`,
|
||||
);
|
||||
}
|
||||
await ctx.interaction.deferReply();
|
||||
const row = createMenu(channel, role);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(message)
|
||||
.setDescription(`Please select your roles below\nYou can select multiple roles`)
|
||||
.setColor(0xcc5279);
|
||||
await channel.send({
|
||||
embeds: [embed],
|
||||
components: [row],
|
||||
});
|
||||
await ctx.interaction.editReply("✅ Done!");
|
||||
},
|
||||
});
|
||||
|
||||
function createMenu(channel: TextChannel, role: Collection<string, Role>) {
|
||||
if (!channel || !role) throw new Error("Missing channel or role");
|
||||
const menu = new StringSelectMenuBuilder()
|
||||
.setCustomId("role-menu")
|
||||
.setMaxValues(role.size)
|
||||
.setMinValues(0)
|
||||
.setPlaceholder("Pick some roles here!")
|
||||
.setOptions(
|
||||
role.map((r) => {
|
||||
return {
|
||||
label: r.name,
|
||||
value: r.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(menu);
|
||||
return row;
|
||||
if (!channel || !role) throw new Error("Missing channel or role");
|
||||
const menu = new StringSelectMenuBuilder()
|
||||
.setCustomId("role-menu")
|
||||
.setMaxValues(role.size)
|
||||
.setMinValues(0)
|
||||
.setPlaceholder("Pick some roles here!")
|
||||
.setOptions(
|
||||
role.map((r) => {
|
||||
return {
|
||||
label: r.name,
|
||||
value: r.id,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(menu);
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -1,59 +1,64 @@
|
||||
import { publish } from '#plugins';
|
||||
import { commandModule, CommandType } from '@sern/handler';
|
||||
import { ActionRowBuilder, codeBlock, ComponentType, inlineCode, StringSelectMenuBuilder } from 'discord.js';
|
||||
import { createWorker } from 'tesseract.js';
|
||||
import { publish } from "#plugins";
|
||||
import { commandModule, CommandType } from "@sern/handler";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
codeBlock,
|
||||
ComponentType,
|
||||
inlineCode,
|
||||
StringSelectMenuBuilder,
|
||||
} from "discord.js";
|
||||
import { createWorker } from "tesseract.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.CtxMsg,
|
||||
plugins: [publish()],
|
||||
description: '',
|
||||
description: "",
|
||||
execute: async (ctx) => {
|
||||
const select = new StringSelectMenuBuilder({
|
||||
customId: 'ocr-lang',
|
||||
placeholder: 'Select language',
|
||||
customId: "ocr-lang",
|
||||
placeholder: "Select language",
|
||||
options: [
|
||||
{ label: 'English', value: 'eng', emoji: '🇺🇸' },
|
||||
{ label: 'Spanish', value: 'spa', emoji: '🇪🇸' },
|
||||
{ label: 'Portuguese', value: 'por', emoji: '🇵🇹' },
|
||||
{ label: 'French', value: 'fra', emoji: '🇫🇷' },
|
||||
{ label: 'German', value: 'deu', emoji: '🇩🇪' },
|
||||
{ label: 'Italian', value: 'ita', emoji: '🇮🇹' },
|
||||
{ label: 'Japanese', value: 'jpn', emoji: '🇯🇵' },
|
||||
]
|
||||
})
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>()
|
||||
.addComponents(select)
|
||||
{ label: "English", value: "eng", emoji: "🇺🇸" },
|
||||
{ label: "Spanish", value: "spa", emoji: "🇪🇸" },
|
||||
{ label: "Portuguese", value: "por", emoji: "🇵🇹" },
|
||||
{ label: "French", value: "fra", emoji: "🇫🇷" },
|
||||
{ label: "German", value: "deu", emoji: "🇩🇪" },
|
||||
{ label: "Italian", value: "ita", emoji: "🇮🇹" },
|
||||
{ label: "Japanese", value: "jpn", emoji: "🇯🇵" },
|
||||
],
|
||||
});
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select);
|
||||
|
||||
const langMsg = await ctx.reply({
|
||||
components: [row],
|
||||
ephemeral: true,
|
||||
})
|
||||
});
|
||||
|
||||
const langCollector = langMsg.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 10000
|
||||
})
|
||||
langCollector.once('collect', async (i) => {
|
||||
const lang = i.values[0]
|
||||
langMsg.delete()
|
||||
time: 10000,
|
||||
});
|
||||
langCollector.once("collect", async (i) => {
|
||||
const lang = i.values[0];
|
||||
langMsg.delete();
|
||||
const readingMessage = await ctx.targetMessage.channel.send({
|
||||
content: `Reading [this image](${ctx.targetMessage.url}) with language ${inlineCode(lang)}...`,
|
||||
})
|
||||
});
|
||||
|
||||
const image = ctx.targetMessage.attachments.first()
|
||||
const image = ctx.targetMessage.attachments.first();
|
||||
if (!image) {
|
||||
await readingMessage.edit({
|
||||
content: 'No image found',
|
||||
})
|
||||
return langCollector.stop('No image found')
|
||||
content: "No image found",
|
||||
});
|
||||
return langCollector.stop("No image found");
|
||||
}
|
||||
|
||||
const worker = await createWorker(lang)
|
||||
const ocrData = await worker.recognize(image.proxyURL)
|
||||
const worker = await createWorker(lang);
|
||||
const ocrData = await worker.recognize(image.proxyURL);
|
||||
await readingMessage.edit({
|
||||
content: `Here's what I was able to read from ${ctx.targetMessage.url}:\n${codeBlock(ocrData.data.text)}`,
|
||||
})
|
||||
await worker.terminate()
|
||||
})
|
||||
});
|
||||
await worker.terminate();
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@ import { publish } from "#plugins";
|
||||
import { Timestamp, slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [publish()],
|
||||
description: "Pong!",
|
||||
execute: async (context) => {
|
||||
const uptime = new Timestamp(context.client.readyTimestamp!);
|
||||
await context.reply(
|
||||
`Pong 🏓 \`${context.client.ws.ping}ms\`\nStarted running ${uptime.getRelativeTime()}`
|
||||
);
|
||||
},
|
||||
plugins: [publish()],
|
||||
description: "Pong!",
|
||||
execute: async (context) => {
|
||||
const uptime = new Timestamp(context.client.readyTimestamp!);
|
||||
await context.reply(
|
||||
`Pong 🏓 \`${context.client.ws.ping}ms\`\nStarted running ${uptime.getRelativeTime()}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,96 +5,97 @@ import type { Plugin } from "typings";
|
||||
import { PluginList } from "#constants";
|
||||
|
||||
export default slashCommand({
|
||||
description: "View sern plugins",
|
||||
options: [
|
||||
{
|
||||
name: "plugin",
|
||||
description: "The plugin to view",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
const plugins = require(PluginList) as Plugin[];
|
||||
description: "View sern plugins",
|
||||
options: [
|
||||
{
|
||||
name: "plugin",
|
||||
description: "The plugin to view",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
const plugins = require(PluginList) as Plugin[];
|
||||
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!plugins.length) return ctx.respond([{ name: "No plugins found", value: "" }]);
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!plugins.length)
|
||||
return ctx.respond([{ name: "No plugins found", value: "" }]);
|
||||
|
||||
const filtered = plugins.filter((p) =>
|
||||
p.name.toLowerCase().includes(focus.toLowerCase())
|
||||
);
|
||||
const filtered = plugins.filter((p) =>
|
||||
p.name.toLowerCase().includes(focus.toLowerCase()),
|
||||
);
|
||||
|
||||
return ctx.respond(
|
||||
filtered.map((p) => ({
|
||||
name: p.name,
|
||||
value: p.link,
|
||||
}))
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
publish(),
|
||||
cooldown.add([["user", "1/10"]], ({ seconds, context }) =>
|
||||
context.reply({
|
||||
content: `You gotta chill for ${seconds} seconds`,
|
||||
ephemeral: true,
|
||||
})
|
||||
),
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const plugins = require(PluginList) as Plugin[];
|
||||
return ctx.respond(
|
||||
filtered.map((p) => ({
|
||||
name: p.name,
|
||||
value: p.link,
|
||||
})),
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
publish(),
|
||||
cooldown.add([["user", "1/10"]], ({ seconds, context }) =>
|
||||
context.reply({
|
||||
content: `You gotta chill for ${seconds} seconds`,
|
||||
ephemeral: true,
|
||||
}),
|
||||
),
|
||||
],
|
||||
async execute(ctx, [, options]) {
|
||||
const plugins = require(PluginList) as Plugin[];
|
||||
|
||||
if (!plugins.length) return ctx.reply("Plugins are uncached, contact Evo!");
|
||||
if (!plugins.length) return ctx.reply("Plugins are uncached, contact Evo!");
|
||||
|
||||
const url = options.getString("plugin", true);
|
||||
const plugin = plugins.find((p) => p.link === url);
|
||||
const url = options.getString("plugin", true);
|
||||
const plugin = plugins.find((p) => p.link === url);
|
||||
|
||||
if (!plugin) {
|
||||
return ctx.reply(`No plugin found at this [link](<${url}>)`);
|
||||
}
|
||||
if (!plugin) {
|
||||
return ctx.reply(`No plugin found at this [link](<${url}>)`);
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("Random")
|
||||
.setTimestamp()
|
||||
.setTitle(plugin.name)
|
||||
.setURL(plugin.link)
|
||||
.setFields(
|
||||
{
|
||||
name: "Description",
|
||||
value: plugin.description,
|
||||
},
|
||||
{
|
||||
name: "Version",
|
||||
value: plugin.version,
|
||||
},
|
||||
{
|
||||
name: "Author",
|
||||
value: plugin.author.map(parseAuthor).join("\n"),
|
||||
},
|
||||
{
|
||||
name: "Example",
|
||||
value: plugin.example,
|
||||
}
|
||||
);
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("Random")
|
||||
.setTimestamp()
|
||||
.setTitle(plugin.name)
|
||||
.setURL(plugin.link)
|
||||
.setFields(
|
||||
{
|
||||
name: "Description",
|
||||
value: plugin.description,
|
||||
},
|
||||
{
|
||||
name: "Version",
|
||||
value: plugin.version,
|
||||
},
|
||||
{
|
||||
name: "Author",
|
||||
value: plugin.author.map(parseAuthor).join("\n"),
|
||||
},
|
||||
{
|
||||
name: "Example",
|
||||
value: plugin.example,
|
||||
},
|
||||
);
|
||||
|
||||
return ctx.reply({
|
||||
embeds: [embed],
|
||||
});
|
||||
},
|
||||
return ctx.reply({
|
||||
embeds: [embed],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function parseAuthor(s: string) {
|
||||
let [ghAuthor, discordAuthor] = s.split(" ");
|
||||
ghAuthor = ghAuthor.replace("@", "");
|
||||
const url = `https://github.com/${ghAuthor}`;
|
||||
return `[${ghAuthor}](${url}) ${discordAuthor}`;
|
||||
let [ghAuthor, discordAuthor] = s.split(" ");
|
||||
ghAuthor = ghAuthor.replace("@", "");
|
||||
const url = `https://github.com/${ghAuthor}`;
|
||||
return `[${ghAuthor}](${url}) ${discordAuthor}`;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
name: string;
|
||||
download_url: string;
|
||||
rawData: string;
|
||||
name: string;
|
||||
download_url: string;
|
||||
rawData: string;
|
||||
}
|
||||
|
||||
@@ -5,22 +5,22 @@ import { slashCommand } from "#utils";
|
||||
import type { Plugin } from "typings";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [
|
||||
publish({
|
||||
dmPermission: false,
|
||||
defaultMemberPermissions: ["Administrator"],
|
||||
}),
|
||||
ownerOnly([Evo]),
|
||||
],
|
||||
description: "refresh plugins cache",
|
||||
async execute(ctx) {
|
||||
await ctx.interaction.deferReply({ ephemeral: true });
|
||||
const size = await cp();
|
||||
if (!size) return ctx.interaction.editReply({ content: "Fetch failed!" });
|
||||
return ctx.interaction.editReply({
|
||||
content: `Refreshed ${size} Plugins!`,
|
||||
});
|
||||
},
|
||||
plugins: [
|
||||
publish({
|
||||
dmPermission: false,
|
||||
defaultMemberPermissions: ["Administrator"],
|
||||
}),
|
||||
ownerOnly([Evo]),
|
||||
],
|
||||
description: "refresh plugins cache",
|
||||
async execute(ctx) {
|
||||
await ctx.interaction.deferReply({ ephemeral: true });
|
||||
const size = await cp();
|
||||
if (!size) return ctx.interaction.editReply({ content: "Fetch failed!" });
|
||||
return ctx.interaction.editReply({
|
||||
content: `Refreshed ${size} Plugins!`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -28,11 +28,11 @@ export default slashCommand({
|
||||
* @returns {Promise<number | null>} The number of plugins fetched
|
||||
*/
|
||||
export async function cp(): Promise<number | null> {
|
||||
const link = `https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json`;
|
||||
const resp = await fetch(link).catch(() => null);
|
||||
if (!resp) return null;
|
||||
const dataArray = (await resp.json()) as Plugin[];
|
||||
const link = `https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json`;
|
||||
const resp = await fetch(link).catch(() => null);
|
||||
if (!resp) return null;
|
||||
const dataArray = (await resp.json()) as Plugin[];
|
||||
|
||||
writeFileSync(PluginList, JSON.stringify(dataArray, null, 2), { flag: "w" });
|
||||
return dataArray.length;
|
||||
writeFileSync(PluginList, JSON.stringify(dataArray, null, 2), { flag: "w" });
|
||||
return dataArray.length;
|
||||
}
|
||||
|
||||
@@ -4,45 +4,45 @@ import { forumID, ownerIDs } from "#constants";
|
||||
import { Timestamp, slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
description: "Solved the issue? Close the post!",
|
||||
plugins: [publish({ guildIds: ["889026545715400705"] }), channelOnly([forumID])],
|
||||
async execute(ctx) {
|
||||
if (!ctx.channel) return;
|
||||
if (!ctx.channel.isThread() || !ctx.channel.parent) return;
|
||||
if (ctx.channel.parent.type !== ChannelType.GuildForum) return;
|
||||
if (!ctx.channel.ownerId) await ctx.channel.fetchOwner();
|
||||
if (!ownerIDs.concat(ctx.channel.ownerId!).includes(ctx.user.id)) return;
|
||||
description: "Solved the issue? Close the post!",
|
||||
plugins: [publish({ guildIds: ["889026545715400705"] }), channelOnly([forumID])],
|
||||
async execute(ctx) {
|
||||
if (!ctx.channel) return;
|
||||
if (!ctx.channel.isThread() || !ctx.channel.parent) return;
|
||||
if (ctx.channel.parent.type !== ChannelType.GuildForum) return;
|
||||
if (!ctx.channel.ownerId) await ctx.channel.fetchOwner();
|
||||
if (!ownerIDs.concat(ctx.channel.ownerId!).includes(ctx.user.id)) return;
|
||||
|
||||
const tag = ctx.channel.parent.availableTags.find((t) => t.name === "Solved");
|
||||
if (!tag)
|
||||
return ctx.reply({
|
||||
content: "Something bad happened, Please contact Evo!",
|
||||
ephemeral: true,
|
||||
});
|
||||
const tag = ctx.channel.parent.availableTags.find((t) => t.name === "Solved");
|
||||
if (!tag)
|
||||
return ctx.reply({
|
||||
content: "Something bad happened, Please contact Evo!",
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
const memberCount = `• \`${ctx.channel.memberCount}\` member(s) participated in this post!`;
|
||||
const msgCount = `• \`${(ctx.channel.messageCount ?? 0) + 1}\` message(s) are present here`;
|
||||
const msgSent = `• \`${
|
||||
(ctx.channel.totalMessageSent ?? 0) + 1
|
||||
}\` message(s) were sent in total here`;
|
||||
const createdAt = `• This post was created ${new Timestamp(
|
||||
ctx.channel.createdTimestamp!
|
||||
).getRelativeTime()}`;
|
||||
const solvedAt = `• This post was solved ${new Timestamp(Date.now()).getRelativeTime()}`;
|
||||
const memberCount = `• \`${ctx.channel.memberCount}\` member(s) participated in this post!`;
|
||||
const msgCount = `• \`${(ctx.channel.messageCount ?? 0) + 1}\` message(s) are present here`;
|
||||
const msgSent = `• \`${
|
||||
(ctx.channel.totalMessageSent ?? 0) + 1
|
||||
}\` message(s) were sent in total here`;
|
||||
const createdAt = `• This post was created ${new Timestamp(
|
||||
ctx.channel.createdTimestamp!,
|
||||
).getRelativeTime()}`;
|
||||
const solvedAt = `• This post was solved ${new Timestamp(Date.now()).getRelativeTime()}`;
|
||||
|
||||
const funstats = `${ctx.channel.memberCount ? memberCount : ""}\n${
|
||||
ctx.channel.messageCount ? msgCount : ""
|
||||
}\n${ctx.channel.totalMessageSent ? msgSent : ""}\n${createdAt}\n${solvedAt}`;
|
||||
const funstats = `${ctx.channel.memberCount ? memberCount : ""}\n${
|
||||
ctx.channel.messageCount ? msgCount : ""
|
||||
}\n${ctx.channel.totalMessageSent ? msgSent : ""}\n${createdAt}\n${solvedAt}`;
|
||||
|
||||
await ctx.reply({
|
||||
content: `This post is now closed, glad the issue got solved!\n\n\n${funstats}`,
|
||||
ephemeral: false,
|
||||
});
|
||||
await ctx.reply({
|
||||
content: `This post is now closed, glad the issue got solved!\n\n\n${funstats}`,
|
||||
ephemeral: false,
|
||||
});
|
||||
|
||||
await ctx.channel
|
||||
.setAppliedTags([...ctx.channel.appliedTags.slice(0, 4), tag.id])
|
||||
.catch(() => null);
|
||||
await ctx.channel.setLocked(true, `Closed by ${ctx.user.tag}`).catch(() => null);
|
||||
await ctx.channel.setArchived(true, `Closed by ${ctx.user.tag}`).catch(() => null);
|
||||
},
|
||||
await ctx.channel
|
||||
.setAppliedTags([...ctx.channel.appliedTags.slice(0, 4), tag.id])
|
||||
.catch(() => null);
|
||||
await ctx.channel.setLocked(true, `Closed by ${ctx.user.tag}`).catch(() => null);
|
||||
await ctx.channel.setArchived(true, `Closed by ${ctx.user.tag}`).catch(() => null);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,95 +6,97 @@ import { Tag, type TagData } from "typings";
|
||||
import { TagList } from "#constants";
|
||||
|
||||
export default slashCommand({
|
||||
description: "Send a tag",
|
||||
plugins: [publish()],
|
||||
options: [
|
||||
{
|
||||
name: "list",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "List all tags",
|
||||
},
|
||||
{
|
||||
name: "send",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Send a tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "Tag you want to send",
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(`./tags.json`)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length ? t.toLowerCase().includes(focus.toLowerCase()) : true
|
||||
)
|
||||
.map((t) => ({ name: t, value: t }))
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
description: "Who should I mention while showing the tag?",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute(ctx, args) {
|
||||
const [, options] = args;
|
||||
const subCmd = options.getSubcommand();
|
||||
switch (subCmd) {
|
||||
case "list": {
|
||||
const file: TagData[] = require(TagList);
|
||||
const embeds = file.map((tag) => {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(tag.name)
|
||||
.setDescription(tag.content)
|
||||
.setColor("Random")
|
||||
.addFields({
|
||||
name: "Keywords",
|
||||
value: tag.keywords.join(", ") || "No keywords!",
|
||||
})
|
||||
.setTimestamp();
|
||||
return embed;
|
||||
});
|
||||
const paginator = new Paginator({ embeds }).setSelectMenuOptions(
|
||||
...Array(embeds.length)
|
||||
.fill(null)
|
||||
.map((_, i) => ({
|
||||
label: embeds[i].data.title!,
|
||||
value: `${i}`,
|
||||
default: i === 0,
|
||||
}))
|
||||
);
|
||||
description: "Send a tag",
|
||||
plugins: [publish()],
|
||||
options: [
|
||||
{
|
||||
name: "list",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "List all tags",
|
||||
},
|
||||
{
|
||||
name: "send",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Send a tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
description: "Tag you want to send",
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(`./tags.json`)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length
|
||||
? t.toLowerCase().includes(focus.toLowerCase())
|
||||
: true,
|
||||
)
|
||||
.map((t) => ({ name: t, value: t })),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: false,
|
||||
description: "Who should I mention while showing the tag?",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute(ctx, args) {
|
||||
const [, options] = args;
|
||||
const subCmd = options.getSubcommand();
|
||||
switch (subCmd) {
|
||||
case "list": {
|
||||
const file: TagData[] = require(TagList);
|
||||
const embeds = file.map((tag) => {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(tag.name)
|
||||
.setDescription(tag.content)
|
||||
.setColor("Random")
|
||||
.addFields({
|
||||
name: "Keywords",
|
||||
value: tag.keywords.join(", ") || "No keywords!",
|
||||
})
|
||||
.setTimestamp();
|
||||
return embed;
|
||||
});
|
||||
const paginator = new Paginator({ embeds }).setSelectMenuOptions(
|
||||
...Array(embeds.length)
|
||||
.fill(null)
|
||||
.map((_, i) => ({
|
||||
label: embeds[i].data.title!,
|
||||
value: `${i}`,
|
||||
default: i === 0,
|
||||
})),
|
||||
);
|
||||
|
||||
return paginator.run(ctx.interaction);
|
||||
}
|
||||
case "send": {
|
||||
const user = options.getUser("target");
|
||||
const mention = user ? `**Tag suggestion for:** ${user}\n\n` : "";
|
||||
const tag = options.getString("tag", true);
|
||||
const file: TagData[] = require(TagList);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return ctx.reply(`No tag found with name __${tag}__`);
|
||||
}
|
||||
return ctx.reply(mention.concat(tagData.content));
|
||||
}
|
||||
}
|
||||
},
|
||||
return paginator.run(ctx.interaction);
|
||||
}
|
||||
case "send": {
|
||||
const user = options.getUser("target");
|
||||
const mention = user ? `**Tag suggestion for:** ${user}\n\n` : "";
|
||||
const tag = options.getString("tag", true);
|
||||
const file: TagData[] = require(TagList);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return ctx.reply(`No tag found with name __${tag}__`);
|
||||
}
|
||||
return ctx.reply(mention.concat(tagData.content));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
ActionRowBuilder,
|
||||
ApplicationCommandOptionType,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import { createRequire } from "module";
|
||||
@@ -14,186 +14,192 @@ import { slashCommand } from "#utils";
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export default slashCommand({
|
||||
description: "Edit tags",
|
||||
plugins: [publish(), ownerOnly([Evo, Seren])],
|
||||
options: [
|
||||
{
|
||||
name: "create",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Create a new tag",
|
||||
},
|
||||
{
|
||||
name: "edit",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Edit an existing tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
description: "The tag to edit",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const filePath = `./tags.json`;
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(filePath)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length ? t.toLowerCase().includes(focus.toLowerCase()) : true
|
||||
)
|
||||
.map((t) => ({ name: t, value: t }))
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Delete an existing tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
description: "The tag to delete",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const filePath = `./tags.json`;
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(filePath)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length ? t.toLowerCase().includes(focus.toLowerCase()) : true
|
||||
)
|
||||
.map((t) => ({ name: t, value: t }))
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute: async (context, args) => {
|
||||
const [, options] = args;
|
||||
const subcmd = options.getSubcommand();
|
||||
description: "Edit tags",
|
||||
plugins: [publish(), ownerOnly([Evo, Seren])],
|
||||
options: [
|
||||
{
|
||||
name: "create",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Create a new tag",
|
||||
},
|
||||
{
|
||||
name: "edit",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Edit an existing tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
description: "The tag to edit",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const filePath = `./tags.json`;
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(filePath)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length
|
||||
? t.toLowerCase().includes(focus.toLowerCase())
|
||||
: true,
|
||||
)
|
||||
.map((t) => ({ name: t, value: t })),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Delete an existing tag",
|
||||
options: [
|
||||
{
|
||||
name: "tag",
|
||||
description: "The tag to delete",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute(ctx) {
|
||||
const filePath = `./tags.json`;
|
||||
const focus = ctx.options.getFocused();
|
||||
if (!existsSync(filePath)) {
|
||||
return ctx.respond([{ name: "No tags found", value: "" }]);
|
||||
} else {
|
||||
const file: TagData[] = require(TagList);
|
||||
const tags = file.map((t) => t.name);
|
||||
return ctx.respond(
|
||||
tags
|
||||
.filter((t) =>
|
||||
focus.length
|
||||
? t.toLowerCase().includes(focus.toLowerCase())
|
||||
: true,
|
||||
)
|
||||
.map((t) => ({ name: t, value: t })),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute: async (context, args) => {
|
||||
const [, options] = args;
|
||||
const subcmd = options.getSubcommand();
|
||||
|
||||
const file: TagData[] = require(TagList);
|
||||
const file: TagData[] = require(TagList);
|
||||
|
||||
if (subcmd === "create") {
|
||||
const modal = new ModalBuilder().setTitle("Tag Creation").setCustomId("@sern/tag/create");
|
||||
if (subcmd === "create") {
|
||||
const modal = new ModalBuilder()
|
||||
.setTitle("Tag Creation")
|
||||
.setCustomId("@sern/tag/create");
|
||||
|
||||
const tagName = new TextInputBuilder()
|
||||
.setCustomId("tag-name")
|
||||
.setLabel("Tag Name")
|
||||
.setRequired()
|
||||
.setPlaceholder("Name of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(32)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
const tagName = new TextInputBuilder()
|
||||
.setCustomId("tag-name")
|
||||
.setLabel("Tag Name")
|
||||
.setRequired()
|
||||
.setPlaceholder("Name of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(32)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
|
||||
const tagContent = new TextInputBuilder()
|
||||
.setCustomId("tag-content")
|
||||
.setLabel("Tag Content")
|
||||
.setRequired()
|
||||
.setPlaceholder("Content of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(1900)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const tagContent = new TextInputBuilder()
|
||||
.setCustomId("tag-content")
|
||||
.setLabel("Tag Content")
|
||||
.setRequired()
|
||||
.setPlaceholder("Content of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(1900)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
const keywords = new TextInputBuilder()
|
||||
.setCustomId("tag-keywords")
|
||||
.setLabel("Tag Keywords")
|
||||
.setPlaceholder("Keywords for Tag, separated by comma")
|
||||
.setMaxLength(200)
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
const keywords = new TextInputBuilder()
|
||||
.setCustomId("tag-keywords")
|
||||
.setLabel("Tag Keywords")
|
||||
.setPlaceholder("Keywords for Tag, separated by comma")
|
||||
.setMaxLength(200)
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
|
||||
const rows = [tagName, tagContent, keywords].map((r) =>
|
||||
new ActionRowBuilder<TextInputBuilder>().addComponents(r)
|
||||
);
|
||||
modal.addComponents(rows);
|
||||
const rows = [tagName, tagContent, keywords].map((r) =>
|
||||
new ActionRowBuilder<TextInputBuilder>().addComponents(r),
|
||||
);
|
||||
modal.addComponents(rows);
|
||||
|
||||
return context.interaction.showModal(modal);
|
||||
}
|
||||
return context.interaction.showModal(modal);
|
||||
}
|
||||
|
||||
if (subcmd === "edit") {
|
||||
const tag = options.getString("tag", true);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return context.reply(`No tag found with name __${tag}__`);
|
||||
}
|
||||
const modal = new ModalBuilder().setTitle("Tag Edit").setCustomId("@sern/tag/edit");
|
||||
if (subcmd === "edit") {
|
||||
const tag = options.getString("tag", true);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return context.reply(`No tag found with name __${tag}__`);
|
||||
}
|
||||
const modal = new ModalBuilder().setTitle("Tag Edit").setCustomId("@sern/tag/edit");
|
||||
|
||||
const tagName = new TextInputBuilder()
|
||||
.setCustomId("tag-name")
|
||||
.setLabel("Tag Name")
|
||||
.setRequired()
|
||||
.setPlaceholder("Name of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(32)
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(tagData.name);
|
||||
const tagName = new TextInputBuilder()
|
||||
.setCustomId("tag-name")
|
||||
.setLabel("Tag Name")
|
||||
.setRequired()
|
||||
.setPlaceholder("Name of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(32)
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(tagData.name);
|
||||
|
||||
const tagContent = new TextInputBuilder()
|
||||
.setCustomId("tag-content")
|
||||
.setLabel("Tag Content")
|
||||
.setRequired()
|
||||
.setPlaceholder("Content of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(1900)
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setValue(tagData.content);
|
||||
const tagContent = new TextInputBuilder()
|
||||
.setCustomId("tag-content")
|
||||
.setLabel("Tag Content")
|
||||
.setRequired()
|
||||
.setPlaceholder("Content of Tag")
|
||||
.setMinLength(3)
|
||||
.setMaxLength(1900)
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setValue(tagData.content);
|
||||
|
||||
const keywords = new TextInputBuilder()
|
||||
.setCustomId("tag-keywords")
|
||||
.setLabel("Tag Keywords")
|
||||
.setPlaceholder("Keywords for Tag, separated by comma")
|
||||
.setMaxLength(200)
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(tagData.keywords.join(", "));
|
||||
const keywords = new TextInputBuilder()
|
||||
.setCustomId("tag-keywords")
|
||||
.setLabel("Tag Keywords")
|
||||
.setPlaceholder("Keywords for Tag, separated by comma")
|
||||
.setMaxLength(200)
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setValue(tagData.keywords.join(", "));
|
||||
|
||||
const rows = [tagName, tagContent, keywords].map((r) =>
|
||||
new ActionRowBuilder<TextInputBuilder>().addComponents(r)
|
||||
);
|
||||
modal.addComponents(rows);
|
||||
context.user.data = { tag };
|
||||
return context.interaction.showModal(modal);
|
||||
}
|
||||
if (subcmd === "delete") {
|
||||
const tag = options.getString("tag", true);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return context.reply("Tag not found");
|
||||
}
|
||||
file.splice(file.indexOf(tagData), 1);
|
||||
writeFileSync(TagList, JSON.stringify(file, null, 2));
|
||||
const rows = [tagName, tagContent, keywords].map((r) =>
|
||||
new ActionRowBuilder<TextInputBuilder>().addComponents(r),
|
||||
);
|
||||
modal.addComponents(rows);
|
||||
context.user.data = { tag };
|
||||
return context.interaction.showModal(modal);
|
||||
}
|
||||
if (subcmd === "delete") {
|
||||
const tag = options.getString("tag", true);
|
||||
const tagData = file.find((t) => t.name === tag);
|
||||
if (!tagData) {
|
||||
return context.reply("Tag not found");
|
||||
}
|
||||
file.splice(file.indexOf(tagData), 1);
|
||||
writeFileSync(TagList, JSON.stringify(file, null, 2));
|
||||
|
||||
return context.reply(`Tag ${tag} deleted`);
|
||||
}
|
||||
},
|
||||
return context.reply(`Tag ${tag} deleted`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
declare module "discord.js" {
|
||||
interface User {
|
||||
data: unknown;
|
||||
}
|
||||
interface User {
|
||||
data: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,143 +5,143 @@ import { readFileSync } from "fs";
|
||||
import { slashCommand } from "#utils";
|
||||
|
||||
export default slashCommand({
|
||||
plugins: [publish()],
|
||||
description: "Get the time of a person.",
|
||||
options: [
|
||||
{
|
||||
name: "create",
|
||||
description: "Create the timezone where you are in the db.",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options: [
|
||||
{
|
||||
name: "timezone",
|
||||
description: "The timezone where you are located.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute: async (autocomplete) => {
|
||||
const input = autocomplete.options.getFocused();
|
||||
plugins: [publish()],
|
||||
description: "Get the time of a person.",
|
||||
options: [
|
||||
{
|
||||
name: "create",
|
||||
description: "Create the timezone where you are in the db.",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options: [
|
||||
{
|
||||
name: "timezone",
|
||||
description: "The timezone where you are located.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
execute: async (autocomplete) => {
|
||||
const input = autocomplete.options.getFocused();
|
||||
|
||||
return autocomplete.respond(fuzz(input)).catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "get",
|
||||
description: "Get the time of a user",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "The user",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
description: "Delete your entry in the database",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
},
|
||||
],
|
||||
execute: async (ctx, [, options]) => {
|
||||
switch (options.getSubcommand()) {
|
||||
case "create": {
|
||||
const reqData = {
|
||||
timezone: options.getString("timezone", true),
|
||||
key: process.env.TIME_KEY!,
|
||||
userid: ctx.user.id,
|
||||
};
|
||||
const request = await fetch("https://api.srizan.dev/sern/newTime", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(reqData),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).catch(() => null);
|
||||
return autocomplete.respond(fuzz(input)).catch(() => null);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "get",
|
||||
description: "Get the time of a user",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "The user",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
description: "Delete your entry in the database",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
},
|
||||
],
|
||||
execute: async (ctx, [, options]) => {
|
||||
switch (options.getSubcommand()) {
|
||||
case "create": {
|
||||
const reqData = {
|
||||
timezone: options.getString("timezone", true),
|
||||
key: process.env.TIME_KEY!,
|
||||
userid: ctx.user.id,
|
||||
};
|
||||
const request = await fetch("https://api.srizan.dev/sern/newTime", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(reqData),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).catch(() => null);
|
||||
|
||||
const data = (await request?.json()) as Record<string, string>;
|
||||
const data = (await request?.json()) as Record<string, string>;
|
||||
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oops, the response errored out for some reason, you could try again...`,
|
||||
ephemeral: true,
|
||||
});
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oops, the response errored out for some reason, you could try again...`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
return ctx.reply({
|
||||
content: data?.ok ?? data?.error ?? "Something went wrong! Please try again",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
case "get": {
|
||||
const option = options.getMember("user") as GuildMember;
|
||||
const request = await fetch(
|
||||
`https://api.srizan.dev/sern/getTime?userid=${option.id}`
|
||||
).catch(() => null);
|
||||
return ctx.reply({
|
||||
content: data?.ok ?? data?.error ?? "Something went wrong! Please try again",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
case "get": {
|
||||
const option = options.getMember("user") as GuildMember;
|
||||
const request = await fetch(
|
||||
`https://api.srizan.dev/sern/getTime?userid=${option.id}`,
|
||||
).catch(() => null);
|
||||
|
||||
const data = (await request?.json()) as APIResponse;
|
||||
const data = (await request?.json()) as APIResponse;
|
||||
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oopsies, I tried to connect to the API, but something went wrong. Try again, it should work`,
|
||||
ephemeral: true,
|
||||
});
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oopsies, I tried to connect to the API, but something went wrong. Try again, it should work`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
if (data.error)
|
||||
return ctx.reply({
|
||||
content: `${option}'s timezone data doesn't exist in the database!`,
|
||||
ephemeral: true,
|
||||
});
|
||||
if (data.error)
|
||||
return ctx.reply({
|
||||
content: `${option}'s timezone data doesn't exist in the database!`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
const dateConvert = new Date().toLocaleString("en-GB", {
|
||||
timeZone: data.timezone,
|
||||
timeStyle: "full",
|
||||
dateStyle: "medium",
|
||||
});
|
||||
const dateConvert = new Date().toLocaleString("en-GB", {
|
||||
timeZone: data.timezone,
|
||||
timeStyle: "full",
|
||||
dateStyle: "medium",
|
||||
});
|
||||
|
||||
return ctx.reply({
|
||||
content: `Current time for ${option} is \`${dateConvert}\``,
|
||||
allowedMentions: { parse: [] },
|
||||
});
|
||||
}
|
||||
case "delete": {
|
||||
const request = await fetch(
|
||||
`https://api.srizan.dev/sern/deleteTime?userid=${ctx.user.id}&key=${process.env.TIME_KEY}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
).catch(() => null);
|
||||
const data = (await request?.json()) as Record<string, string>;
|
||||
return ctx.reply({
|
||||
content: `Current time for ${option} is \`${dateConvert}\``,
|
||||
allowedMentions: { parse: [] },
|
||||
});
|
||||
}
|
||||
case "delete": {
|
||||
const request = await fetch(
|
||||
`https://api.srizan.dev/sern/deleteTime?userid=${ctx.user.id}&key=${process.env.TIME_KEY}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
).catch(() => null);
|
||||
const data = (await request?.json()) as Record<string, string>;
|
||||
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oops, the response errored out for some reason, you could try again...`,
|
||||
ephemeral: true,
|
||||
});
|
||||
if (!data)
|
||||
return ctx.reply({
|
||||
content: `Oops, the response errored out for some reason, you could try again...`,
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
return ctx.reply({
|
||||
content: data?.ok ?? data?.error ?? "Something went wrong! Please try again",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
return ctx.reply({
|
||||
content: data?.ok ?? data?.error ?? "Something went wrong! Please try again",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function fuzz(s: string, locale = false) {
|
||||
const path = `./time/${locale ? "countrylocalecodes" : "timezone"}.txt`;
|
||||
const path = `./time/${locale ? "countrylocalecodes" : "timezone"}.txt`;
|
||||
|
||||
let zones: string[] = JSON.parse(`${readFileSync(path)}`);
|
||||
zones = zones.filter((choice) => choice.toLowerCase().includes(s.toLowerCase()));
|
||||
return zones.slice(0, 25).map((z) => ({ name: z, value: z }));
|
||||
let zones: string[] = JSON.parse(`${readFileSync(path)}`);
|
||||
zones = zones.filter((choice) => choice.toLowerCase().includes(s.toLowerCase()));
|
||||
return zones.slice(0, 25).map((z) => ({ name: z, value: z }));
|
||||
}
|
||||
|
||||
interface APIResponse {
|
||||
error?: string;
|
||||
timezone?: string;
|
||||
error?: string;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ export const Mina = "504698587221852172";
|
||||
export const ownerIDs = [Evo, Seren, Ropox];
|
||||
export const forumID = "1019807803935825922";
|
||||
export const enum Emojis {
|
||||
PROpen = "<:pr_open:1101708598570143754>",
|
||||
PRClosed = "<:pr_closed:1101708712072183819>",
|
||||
PRMerged = "<:pr_merged:1101708800848830525>",
|
||||
PRDraft = "<:pr_draft:1101708908747309126>",
|
||||
IssueOpen = "<:issue_open:1101709315955511346>",
|
||||
IssueClosed = "<:issue_closed:1101716515771920424>",
|
||||
IssueNotPlanned = "<:issue_notplanned:1101719419434045540>",
|
||||
PROpen = "<:pr_open:1101708598570143754>",
|
||||
PRClosed = "<:pr_closed:1101708712072183819>",
|
||||
PRMerged = "<:pr_merged:1101708800848830525>",
|
||||
PRDraft = "<:pr_draft:1101708908747309126>",
|
||||
IssueOpen = "<:issue_open:1101709315955511346>",
|
||||
IssueClosed = "<:issue_closed:1101716515771920424>",
|
||||
IssueNotPlanned = "<:issue_notplanned:1101719419434045540>",
|
||||
}
|
||||
|
||||
export const PluginList = `${process.cwd()}/pluginlist.json`;
|
||||
export const TagList = `${process.cwd()}/tags.json`;
|
||||
export const TagList = `${process.cwd()}/tags.json`;
|
||||
|
||||
27
src/dependencies.d.ts
vendored
27
src/dependencies.d.ts
vendored
@@ -4,17 +4,24 @@
|
||||
* Service(s) api rely on this file to provide a better developer experience.
|
||||
*/
|
||||
|
||||
import type { SernEmitter, Logging, CoreModuleStore, ModuleManager, ErrorHandling, CoreDependencies, Singleton } from '@sern/handler'
|
||||
import type { Client } from 'discord.js'
|
||||
import type { SernLogger } from './utils/Logger';
|
||||
import type { Octokit } from '@octokit/rest'
|
||||
import type {
|
||||
SernEmitter,
|
||||
Logging,
|
||||
CoreModuleStore,
|
||||
ModuleManager,
|
||||
ErrorHandling,
|
||||
CoreDependencies,
|
||||
Singleton,
|
||||
} from "@sern/handler";
|
||||
import type { Client } from "discord.js";
|
||||
import type { SernLogger } from "./utils/Logger";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
declare global {
|
||||
interface Dependencies extends Dependencies {
|
||||
"@sern/client": Singleton<Client>;
|
||||
"@sern/logger": Singleton<SernLogger>;
|
||||
octokit: Singleton<Octokit>;
|
||||
interface Dependencies extends Dependencies {
|
||||
"@sern/client": Singleton<Client>;
|
||||
"@sern/logger": Singleton<SernLogger>;
|
||||
octokit: Singleton<Octokit>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {}
|
||||
export {};
|
||||
|
||||
@@ -4,32 +4,33 @@ import { onCorrectThread } from "../plugins/onCorrectThread.js";
|
||||
import { forumID } from "#constants";
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Discord,
|
||||
plugins: [onCorrectThread(forumID)],
|
||||
name: "threadCreate",
|
||||
async execute(thread: AnyThreadChannel, _: boolean) {
|
||||
if (thread.appliedTags.length > 3) await thread.setAppliedTags(thread.appliedTags.slice(0, 3));
|
||||
type: EventType.Discord,
|
||||
plugins: [onCorrectThread(forumID)],
|
||||
name: "threadCreate",
|
||||
async execute(thread: AnyThreadChannel, _: boolean) {
|
||||
if (thread.appliedTags.length > 3)
|
||||
await thread.setAppliedTags(thread.appliedTags.slice(0, 3));
|
||||
|
||||
const msg = await thread.fetchStarterMessage().catch(() => null);
|
||||
if (!msg) return thread.setLocked(true);
|
||||
const msg = await thread.fetchStarterMessage().catch(() => null);
|
||||
if (!msg) return thread.setLocked(true);
|
||||
|
||||
const list = `• What is your [\`discord.js\`](https://discord.js.org/) version?\n• What is your [\`@sern/handler\`](https://sern.dev) version?\n• If any error is occuring, what error?`;
|
||||
const list = `• What is your [\`discord.js\`](https://discord.js.org/) version?\n• What is your [\`@sern/handler\`](https://sern.dev) version?\n• If any error is occuring, what error?`;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(
|
||||
`Hello ${msg.author}! Thank you for creating a dedicated post for your issue\n` +
|
||||
`Please make sure you've read the **__Post Guidelines__**!\n` +
|
||||
`In the meanwhile you wait for your answer, It is recommended to provide details as much as possible\n`
|
||||
.concat(list)
|
||||
.concat(`\n\n\nIssue Solved? Run </solved:1026499792194510939>`)
|
||||
)
|
||||
.setColor("Random")
|
||||
.setTimestamp()
|
||||
.setThumbnail(msg.client.user!.displayAvatarURL({ size: 2048 }))
|
||||
.setTitle("Things you should know");
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(
|
||||
`Hello ${msg.author}! Thank you for creating a dedicated post for your issue\n` +
|
||||
`Please make sure you've read the **__Post Guidelines__**!\n` +
|
||||
`In the meanwhile you wait for your answer, It is recommended to provide details as much as possible\n`
|
||||
.concat(list)
|
||||
.concat(`\n\n\nIssue Solved? Run </solved:1026499792194510939>`),
|
||||
)
|
||||
.setColor("Random")
|
||||
.setTimestamp()
|
||||
.setThumbnail(msg.client.user!.displayAvatarURL({ size: 2048 }))
|
||||
.setTitle("Things you should know");
|
||||
|
||||
await msg.reply({
|
||||
embeds: [embed],
|
||||
});
|
||||
},
|
||||
await msg.reply({
|
||||
embeds: [embed],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,14 +2,14 @@ import { eventModule, EventType, Service } from "@sern/handler";
|
||||
import type { GuildMember } from "discord.js";
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Discord,
|
||||
name: "guildMemberAdd",
|
||||
async execute(member: GuildMember) {
|
||||
// TODO: This should be inferred
|
||||
if (member.pending) return;
|
||||
const logger = Service("@sern/logger");
|
||||
logger.info({ message: `${member.user.username} joined` });
|
||||
const requiredRoles = ["980118655738212407"];
|
||||
await member.roles.add(requiredRoles);
|
||||
},
|
||||
type: EventType.Discord,
|
||||
name: "guildMemberAdd",
|
||||
async execute(member: GuildMember) {
|
||||
// TODO: This should be inferred
|
||||
if (member.pending) return;
|
||||
const logger = Service("@sern/logger");
|
||||
logger.info({ message: `${member.user.username} joined` });
|
||||
const requiredRoles = ["980118655738212407"];
|
||||
await member.roles.add(requiredRoles);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import {
|
||||
controller,
|
||||
EventControlPlugin,
|
||||
eventModule,
|
||||
EventType,
|
||||
Payload,
|
||||
PayloadType,
|
||||
controller,
|
||||
EventControlPlugin,
|
||||
eventModule,
|
||||
EventType,
|
||||
Payload,
|
||||
PayloadType,
|
||||
Service,
|
||||
} from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
name: "module.activate",
|
||||
type: EventType.Sern,
|
||||
plugins: [filterFailedActivation()],
|
||||
execute(payload: Payload & { type: PayloadType.Failure }) {
|
||||
const logger = Service("@sern/logger");
|
||||
logger.warning({
|
||||
message: `A module (${payload.module?.name} failed to execute: ${payload.reason}`,
|
||||
});
|
||||
},
|
||||
name: "module.activate",
|
||||
type: EventType.Sern,
|
||||
plugins: [filterFailedActivation()],
|
||||
execute(payload: Payload & { type: PayloadType.Failure }) {
|
||||
const logger = Service("@sern/logger");
|
||||
logger.warning({
|
||||
message: `A module (${payload.module?.name} failed to execute: ${payload.reason}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function filterFailedActivation() {
|
||||
return EventControlPlugin<EventType.Sern>((payload) => {
|
||||
if (payload.type == PayloadType.Failure) {
|
||||
return controller.next();
|
||||
} else {
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
return EventControlPlugin<EventType.Sern>((payload) => {
|
||||
if (payload.type == PayloadType.Failure) {
|
||||
return controller.next();
|
||||
} else {
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,63 +8,63 @@ const require = createRequire(import.meta.url);
|
||||
const file: TagData[] = require(TagList);
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Discord,
|
||||
name: "messageCreate",
|
||||
async execute(message: Message) {
|
||||
if (message.webhookId || message.author?.bot) return;
|
||||
type: EventType.Discord,
|
||||
name: "messageCreate",
|
||||
async execute(message: Message) {
|
||||
if (message.webhookId || message.author?.bot) return;
|
||||
|
||||
const fuzz = new FuzzyMatcher(message, file);
|
||||
const data = fuzz.fuzzyMatch();
|
||||
if (!data) return;
|
||||
const { tag, confidence } = data;
|
||||
if (confidence <= 0.7) return;
|
||||
const fuzz = new FuzzyMatcher(message, file);
|
||||
const data = fuzz.fuzzyMatch();
|
||||
if (!data) return;
|
||||
const { tag, confidence } = data;
|
||||
if (confidence <= 0.7) return;
|
||||
|
||||
if (message.author.data && (message.author.data as { inCooldown: boolean }).inCooldown)
|
||||
return message.react("🌿");
|
||||
if (message.author.data && (message.author.data as { inCooldown: boolean }).inCooldown)
|
||||
return message.react("🌿");
|
||||
|
||||
const mention = fuzz.mentionedUser;
|
||||
const text = mention ? `*Tag suggestion for:* ${mention}\n\n` : ``;
|
||||
const button = new ButtonBuilder()
|
||||
.setLabel("Click here to Jump to message")
|
||||
.setURL(message.url)
|
||||
.setStyle(ButtonStyle.Link);
|
||||
const mention = fuzz.mentionedUser;
|
||||
const text = mention ? `*Tag suggestion for:* ${mention}\n\n` : ``;
|
||||
const button = new ButtonBuilder()
|
||||
.setLabel("Click here to Jump to message")
|
||||
.setURL(message.url)
|
||||
.setStyle(ButtonStyle.Link);
|
||||
|
||||
const deleteButton = new ButtonBuilder()
|
||||
.setLabel("Didn't help? False Trigger?")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId("@falseTrigger");
|
||||
const deleteButton = new ButtonBuilder()
|
||||
.setLabel("Didn't help? False Trigger?")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId("@falseTrigger");
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().setComponents([button, deleteButton]);
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().setComponents([button, deleteButton]);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(tag.content.trim())
|
||||
.setFooter({
|
||||
text: `${message.author.tag} | Confidence: ${(confidence * 100).toFixed(2)}%`,
|
||||
iconURL: message.author.displayAvatarURL(),
|
||||
})
|
||||
.setColor("Random")
|
||||
.setTimestamp();
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(tag.content.trim())
|
||||
.setFooter({
|
||||
text: `${message.author.tag} | Confidence: ${(confidence * 100).toFixed(2)}%`,
|
||||
iconURL: message.author.displayAvatarURL(),
|
||||
})
|
||||
.setColor("Random")
|
||||
.setTimestamp();
|
||||
|
||||
message.author.data = {
|
||||
inCooldown: true,
|
||||
};
|
||||
tag.embed ??= true;
|
||||
message.author.data = {
|
||||
inCooldown: true,
|
||||
};
|
||||
tag.embed ??= true;
|
||||
|
||||
const msg = await message.channel.send({
|
||||
content: tag.embed ? text : tag.content,
|
||||
embeds: tag.embed ? [embed] : [],
|
||||
components: [row],
|
||||
allowedMentions: {
|
||||
parse: [],
|
||||
},
|
||||
});
|
||||
const msg = await message.channel.send({
|
||||
content: tag.embed ? text : tag.content,
|
||||
embeds: tag.embed ? [embed] : [],
|
||||
components: [row],
|
||||
allowedMentions: {
|
||||
parse: [],
|
||||
},
|
||||
});
|
||||
|
||||
(msg as TagMessage).tagTriggerId = message.author.id;
|
||||
(msg as TagMessage).tagTriggerId = message.author.id;
|
||||
|
||||
setTimeout(() => {
|
||||
message.author.data = {
|
||||
inCooldown: false,
|
||||
};
|
||||
}, 15_000);
|
||||
},
|
||||
setTimeout(() => {
|
||||
message.author.data = {
|
||||
inCooldown: false,
|
||||
};
|
||||
}, 15_000);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,15 +3,15 @@ import type { Message } from "discord.js";
|
||||
const wait = (await import("util")).promisify(setTimeout);
|
||||
|
||||
export default eventModule({
|
||||
name: "messageCreate",
|
||||
type: EventType.Discord,
|
||||
async execute(message: Message) {
|
||||
if (message.author?.bot || message.webhookId) return;
|
||||
const regex = /(?:w+h+a+t+)?(?:'s+| is+|s+|’s+)? ?(?:.*)?b+o+f+a+/gim;
|
||||
const rand = Math.random() * 10000;
|
||||
if (message.content.match(regex)) {
|
||||
await wait(rand);
|
||||
return message.reply("bofa deez nuts");
|
||||
}
|
||||
},
|
||||
name: "messageCreate",
|
||||
type: EventType.Discord,
|
||||
async execute(message: Message) {
|
||||
if (message.author?.bot || message.webhookId) return;
|
||||
const regex = /(?:w+h+a+t+)?(?:'s+| is+|s+|’s+)? ?(?:.*)?b+o+f+a+/gim;
|
||||
const rand = Math.random() * 10000;
|
||||
if (message.content.match(regex)) {
|
||||
await wait(rand);
|
||||
return message.reply("bofa deez nuts");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { eventModule, EventType } from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
emitter: "process",
|
||||
type: EventType.External,
|
||||
execute(r) {
|
||||
console.log(r);
|
||||
},
|
||||
emitter: "process",
|
||||
type: EventType.External,
|
||||
execute(r) {
|
||||
console.log(r);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { eventModule, EventType } from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
emitter: "process",
|
||||
type: EventType.External,
|
||||
execute(r) {
|
||||
console.log(r);
|
||||
},
|
||||
emitter: "process",
|
||||
type: EventType.External,
|
||||
execute(r) {
|
||||
console.log(r);
|
||||
},
|
||||
});
|
||||
|
||||
40
src/index.ts
40
src/index.ts
@@ -1,34 +1,36 @@
|
||||
import "dotenv/config";
|
||||
import { Client, GatewayIntentBits, Partials } from "discord.js";
|
||||
import { Sern, single, makeDependencies, Service } from "@sern/handler";
|
||||
import { SernLogger } from "#utils";
|
||||
import { SernLogger } from "#utils";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { cp } from "./commands/refresh.js";
|
||||
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
partials: [Partials.GuildMember, Partials.Message, Partials.ThreadMember, Partials.Channel],
|
||||
sweepers: {
|
||||
messages: {
|
||||
interval: 43200,
|
||||
lifetime: 21600,
|
||||
},
|
||||
},
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
partials: [Partials.GuildMember, Partials.Message, Partials.ThreadMember, Partials.Channel],
|
||||
sweepers: {
|
||||
messages: {
|
||||
interval: 43200,
|
||||
lifetime: 21600,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
await makeDependencies({
|
||||
build: (root) =>
|
||||
root.add({ "@sern/client": () => client })
|
||||
root
|
||||
.add({ "@sern/client": () => client })
|
||||
.upsert({ "@sern/logger": () => new SernLogger("info") })
|
||||
.add({ process: () => process,
|
||||
octokit: () => new Octokit({ auth: process.env.GITHUB_TOKEN }) })
|
||||
});
|
||||
.add({
|
||||
process: () => process,
|
||||
octokit: () => new Octokit({ auth: process.env.GITHUB_TOKEN }),
|
||||
}),
|
||||
});
|
||||
|
||||
Sern.init({
|
||||
defaultPrefix: "sern",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
|
||||
|
||||
export function channelOnly(channelIds: string[], onFail?: string) {
|
||||
return CommandControlPlugin<CommandType.Both>((ctx, _) => {
|
||||
if (!ctx.channel) throw new Error("Channel not found!");
|
||||
if (
|
||||
!channelIds.includes(ctx.channel.id) &&
|
||||
ctx.channel.isThread() &&
|
||||
!channelIds.includes(ctx.channel.parentId!)
|
||||
) {
|
||||
onFail ? ctx.reply({ content: onFail, ephemeral: true }) : null;
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
return CommandControlPlugin<CommandType.Both>((ctx, _) => {
|
||||
if (!ctx.channel) throw new Error("Channel not found!");
|
||||
if (
|
||||
!channelIds.includes(ctx.channel.id) &&
|
||||
ctx.channel.isThread() &&
|
||||
!channelIds.includes(ctx.channel.parentId!)
|
||||
) {
|
||||
onFail ? ctx.reply({ content: onFail, ephemeral: true }) : null;
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,124 +24,124 @@ import { GuildMember } from "discord.js";
|
||||
*/
|
||||
export type CooldownString = `${number}/${number}`;
|
||||
export interface Cooldown {
|
||||
location: CooldownLocation;
|
||||
seconds: number;
|
||||
actions: number;
|
||||
location: CooldownLocation;
|
||||
seconds: number;
|
||||
actions: number;
|
||||
}
|
||||
export enum CooldownLocation {
|
||||
channel = "channel",
|
||||
user = "user",
|
||||
guild = "guild",
|
||||
channel = "channel",
|
||||
user = "user",
|
||||
guild = "guild",
|
||||
}
|
||||
|
||||
export class ExpiryMap<K, V> extends Map<K, V> {
|
||||
public readonly expiry: number;
|
||||
constructor(expiry: number = Infinity, iterable: [K, V][] | ReadonlyMap<K, V> = []) {
|
||||
super(iterable);
|
||||
this.expiry = expiry;
|
||||
}
|
||||
public readonly expiry: number;
|
||||
constructor(expiry: number = Infinity, iterable: [K, V][] | ReadonlyMap<K, V> = []) {
|
||||
super(iterable);
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
public set(key: K, value: V, expiry: number = this.expiry): this {
|
||||
super.set(key, value);
|
||||
if (expiry !== Infinity)
|
||||
setTimeout(() => {
|
||||
super.delete(key);
|
||||
}, expiry);
|
||||
return this;
|
||||
}
|
||||
public set(key: K, value: V, expiry: number = this.expiry): this {
|
||||
super.set(key, value);
|
||||
if (expiry !== Infinity)
|
||||
setTimeout(() => {
|
||||
super.delete(key);
|
||||
}, expiry);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export const map = new ExpiryMap<string, number>();
|
||||
|
||||
function parseCooldown(location: CooldownLocation, cooldown: CooldownString): Cooldown {
|
||||
const [actions, seconds] = cooldown.split("/").map((s) => Number(s));
|
||||
const [actions, seconds] = cooldown.split("/").map((s) => Number(s));
|
||||
|
||||
if (
|
||||
!Number.isSafeInteger(actions) ||
|
||||
!Number.isSafeInteger(seconds) ||
|
||||
actions === undefined ||
|
||||
seconds === undefined
|
||||
) {
|
||||
throw new Error(`Invalid cooldown string: ${cooldown}`);
|
||||
}
|
||||
if (
|
||||
!Number.isSafeInteger(actions) ||
|
||||
!Number.isSafeInteger(seconds) ||
|
||||
actions === undefined ||
|
||||
seconds === undefined
|
||||
) {
|
||||
throw new Error(`Invalid cooldown string: ${cooldown}`);
|
||||
}
|
||||
|
||||
return {
|
||||
actions,
|
||||
seconds,
|
||||
location,
|
||||
};
|
||||
return {
|
||||
actions,
|
||||
seconds,
|
||||
location,
|
||||
};
|
||||
}
|
||||
|
||||
function getPropertyForLocation(context: Context, location: CooldownLocation) {
|
||||
switch (location) {
|
||||
case CooldownLocation.channel:
|
||||
return context.channel!.id;
|
||||
case CooldownLocation.user:
|
||||
if (!context.member || !(context.member instanceof GuildMember)) {
|
||||
throw new Error("context.member is not a GuildMember");
|
||||
}
|
||||
return context.member.id;
|
||||
case CooldownLocation.guild:
|
||||
return context.guildId;
|
||||
}
|
||||
switch (location) {
|
||||
case CooldownLocation.channel:
|
||||
return context.channel!.id;
|
||||
case CooldownLocation.user:
|
||||
if (!context.member || !(context.member instanceof GuildMember)) {
|
||||
throw new Error("context.member is not a GuildMember");
|
||||
}
|
||||
return context.member.id;
|
||||
case CooldownLocation.guild:
|
||||
return context.guildId;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RecievedCooldown {
|
||||
location: CooldownLocation;
|
||||
actions: number;
|
||||
maxActions: number;
|
||||
seconds: number;
|
||||
context: Context;
|
||||
location: CooldownLocation;
|
||||
actions: number;
|
||||
maxActions: number;
|
||||
seconds: number;
|
||||
context: Context;
|
||||
}
|
||||
type CooldownResponse = (cooldown: RecievedCooldown) => any;
|
||||
|
||||
function add(
|
||||
items: Array<[CooldownLocation | keyof typeof CooldownLocation, CooldownString] | Cooldown>,
|
||||
message?: CooldownResponse
|
||||
items: Array<[CooldownLocation | keyof typeof CooldownLocation, CooldownString] | Cooldown>,
|
||||
message?: CooldownResponse,
|
||||
) {
|
||||
const raw = items.map((c) => {
|
||||
if (!Array.isArray(c)) return c;
|
||||
return parseCooldown(c[0] as CooldownLocation, c[1]);
|
||||
}) as Array<Cooldown>;
|
||||
return CommandControlPlugin<CommandType.Both>(async (context, args) => {
|
||||
for (const { location, actions, seconds } of raw) {
|
||||
const id = getPropertyForLocation(context, location);
|
||||
const cooldown = map.get(id!);
|
||||
const raw = items.map((c) => {
|
||||
if (!Array.isArray(c)) return c;
|
||||
return parseCooldown(c[0] as CooldownLocation, c[1]);
|
||||
}) as Array<Cooldown>;
|
||||
return CommandControlPlugin<CommandType.Both>(async (context, args) => {
|
||||
for (const { location, actions, seconds } of raw) {
|
||||
const id = getPropertyForLocation(context, location);
|
||||
const cooldown = map.get(id!);
|
||||
|
||||
if (!cooldown) {
|
||||
map.set(id!, 1, seconds * 1000);
|
||||
continue;
|
||||
}
|
||||
if (!cooldown) {
|
||||
map.set(id!, 1, seconds * 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cooldown >= actions) {
|
||||
if (message) {
|
||||
await message({
|
||||
location,
|
||||
actions: cooldown,
|
||||
maxActions: actions,
|
||||
seconds,
|
||||
context,
|
||||
});
|
||||
}
|
||||
return controller.stop();
|
||||
}
|
||||
if (cooldown >= actions) {
|
||||
if (message) {
|
||||
await message({
|
||||
location,
|
||||
actions: cooldown,
|
||||
maxActions: actions,
|
||||
seconds,
|
||||
context,
|
||||
});
|
||||
}
|
||||
return controller.stop();
|
||||
}
|
||||
|
||||
map.set(id!, cooldown + 1, seconds * 1000);
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
map.set(id!, cooldown + 1, seconds * 1000);
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
}
|
||||
|
||||
type Location = (value: CooldownString) => ReturnType<typeof add>;
|
||||
|
||||
const locations: Record<CooldownLocation, Location> = {
|
||||
[CooldownLocation.channel]: (value) => add([[CooldownLocation.channel, value]]),
|
||||
[CooldownLocation.user]: (value) => add([[CooldownLocation.user, value]]),
|
||||
[CooldownLocation.guild]: (value) => add([[CooldownLocation.guild, value]]),
|
||||
[CooldownLocation.channel]: (value) => add([[CooldownLocation.channel, value]]),
|
||||
[CooldownLocation.user]: (value) => add([[CooldownLocation.user, value]]),
|
||||
[CooldownLocation.guild]: (value) => add([[CooldownLocation.guild, value]]),
|
||||
};
|
||||
|
||||
export const cooldown = {
|
||||
add,
|
||||
locations,
|
||||
map,
|
||||
add,
|
||||
locations,
|
||||
map,
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@ import { controller, DiscordEventControlPlugin } from "@sern/handler";
|
||||
import { ChannelType } from "discord.js";
|
||||
|
||||
export function onCorrectThread(parentId: string) {
|
||||
return DiscordEventControlPlugin("threadCreate", (thread, newlyMade) => {
|
||||
const isBadThread =
|
||||
!thread.parent ||
|
||||
thread.parentId !== parentId ||
|
||||
thread.parent.type !== ChannelType.GuildForum ||
|
||||
!newlyMade;
|
||||
if (!isBadThread) {
|
||||
return controller.next();
|
||||
}
|
||||
return controller.stop();
|
||||
});
|
||||
return DiscordEventControlPlugin("threadCreate", (thread, newlyMade) => {
|
||||
const isBadThread =
|
||||
!thread.parent ||
|
||||
thread.parentId !== parentId ||
|
||||
thread.parent.type !== ChannelType.GuildForum ||
|
||||
!newlyMade;
|
||||
if (!isBadThread) {
|
||||
return controller.next();
|
||||
}
|
||||
return controller.stop();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,18 +21,18 @@ import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
|
||||
import { ownerIDs } from "#constants";
|
||||
|
||||
function map(s: string[]) {
|
||||
const userMention = (s: string) => `<@!${s}>`;
|
||||
return s.map((id) => `\` - \` ${userMention(id)}`).join("\n");
|
||||
const userMention = (s: string) => `<@!${s}>`;
|
||||
return s.map((id) => `\` - \` ${userMention(id)}`).join("\n");
|
||||
}
|
||||
export function ownerOnly(override?: string[]) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
|
||||
if ((override ?? ownerIDs).includes(ctx.user.id)) return controller.next();
|
||||
//* If you want to reply when the command fails due to user not being owner, you can use following
|
||||
await ctx.reply({
|
||||
content: `Not for you! Only these users can run this\n${map(override ?? ownerIDs)}`,
|
||||
ephemeral: true,
|
||||
allowedMentions: { repliedUser: false },
|
||||
});
|
||||
return controller.stop(); //! Important: It stops the execution of command!
|
||||
});
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
|
||||
if ((override ?? ownerIDs).includes(ctx.user.id)) return controller.next();
|
||||
//* If you want to reply when the command fails due to user not being owner, you can use following
|
||||
await ctx.reply({
|
||||
content: `Not for you! Only these users can run this\n${map(override ?? ownerIDs)}`,
|
||||
ephemeral: true,
|
||||
allowedMentions: { repliedUser: false },
|
||||
});
|
||||
return controller.stop(); //! Important: It stops the execution of command!
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,196 +20,178 @@
|
||||
* @end
|
||||
*/
|
||||
import {
|
||||
CommandInitPlugin,
|
||||
CommandType,
|
||||
controller,
|
||||
SernOptionsData,
|
||||
SlashCommand,
|
||||
Service,
|
||||
CommandInitPlugin,
|
||||
CommandType,
|
||||
controller,
|
||||
SernOptionsData,
|
||||
SlashCommand,
|
||||
Service,
|
||||
} from "@sern/handler";
|
||||
import {
|
||||
ApplicationCommandData,
|
||||
ApplicationCommandType,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionResolvable,
|
||||
ApplicationCommandData,
|
||||
ApplicationCommandType,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionResolvable,
|
||||
} from "discord.js";
|
||||
|
||||
export const CommandTypeRaw = {
|
||||
[CommandType.Both]: ApplicationCommandType.ChatInput,
|
||||
[CommandType.CtxUser]: ApplicationCommandType.User,
|
||||
[CommandType.CtxMsg]: ApplicationCommandType.Message,
|
||||
[CommandType.Slash]: ApplicationCommandType.ChatInput,
|
||||
[CommandType.Both]: ApplicationCommandType.ChatInput,
|
||||
[CommandType.CtxUser]: ApplicationCommandType.User,
|
||||
[CommandType.CtxMsg]: ApplicationCommandType.Message,
|
||||
[CommandType.Slash]: ApplicationCommandType.ChatInput,
|
||||
} as const;
|
||||
|
||||
export function publish<
|
||||
T extends
|
||||
| CommandType.Both
|
||||
| CommandType.Slash
|
||||
| CommandType.CtxMsg
|
||||
| CommandType.CtxUser,
|
||||
T extends CommandType.Both | CommandType.Slash | CommandType.CtxMsg | CommandType.CtxUser,
|
||||
>(options?: PublishOptions) {
|
||||
return CommandInitPlugin<T>(async ({ module }) => {
|
||||
// Users need to provide their own useContainer function.
|
||||
let client;
|
||||
try {
|
||||
client = (await import("@sern/handler")).Service("@sern/client");
|
||||
} catch {
|
||||
const { useContainer } = await import("../index.js");
|
||||
client = useContainer("@sern/client")[0];
|
||||
}
|
||||
const defaultOptions = {
|
||||
guildIds: [],
|
||||
dmPermission: undefined,
|
||||
defaultMemberPermissions: null,
|
||||
};
|
||||
return CommandInitPlugin<T>(async ({ module }) => {
|
||||
// Users need to provide their own useContainer function.
|
||||
let client;
|
||||
try {
|
||||
client = (await import("@sern/handler")).Service("@sern/client");
|
||||
} catch {
|
||||
const { useContainer } = await import("../index.js");
|
||||
client = useContainer("@sern/client")[0];
|
||||
}
|
||||
const defaultOptions = {
|
||||
guildIds: [],
|
||||
dmPermission: undefined,
|
||||
defaultMemberPermissions: null,
|
||||
};
|
||||
|
||||
options = { ...defaultOptions, ...options } as PublishOptions &
|
||||
ValidPublishOptions;
|
||||
let { defaultMemberPermissions, dmPermission, guildIds } =
|
||||
options as unknown as ValidPublishOptions;
|
||||
options = { ...defaultOptions, ...options } as PublishOptions & ValidPublishOptions;
|
||||
let { defaultMemberPermissions, dmPermission, guildIds } =
|
||||
options as unknown as ValidPublishOptions;
|
||||
|
||||
function c(e: unknown) {
|
||||
console.error("publish command didnt work for", module.name);
|
||||
console.error(e);
|
||||
}
|
||||
function c(e: unknown) {
|
||||
console.error("publish command didnt work for", module.name);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const log =
|
||||
(...message: any[]) =>
|
||||
() =>
|
||||
console.log(...message);
|
||||
const logged = (...message: any[]) => log(message);
|
||||
/**
|
||||
* a local function that returns either one value or the other,
|
||||
* depending on {t}'s CommandType. If the commandtype of
|
||||
* this module is CommandType.Both or CommandType.Text or CommandType.Slash,
|
||||
* return 'is', else return 'els'
|
||||
* @param t
|
||||
* @returns S | T
|
||||
*/
|
||||
const appCmd = <V extends CommandType, S, T>(t: V) => {
|
||||
return (is: S, els: T) => ((t & CommandType.Both) !== 0 ? is : els);
|
||||
};
|
||||
const curAppType = CommandTypeRaw[module.type];
|
||||
const createCommandData = () => {
|
||||
const cmd = appCmd(module.type);
|
||||
return {
|
||||
name: module.name,
|
||||
type: curAppType,
|
||||
description: cmd(module.description, ""),
|
||||
options: cmd(
|
||||
optionsTransformer((module as SlashCommand).options ?? []),
|
||||
[],
|
||||
),
|
||||
defaultMemberPermissions,
|
||||
dmPermission,
|
||||
} as ApplicationCommandData;
|
||||
};
|
||||
const log =
|
||||
(...message: any[]) =>
|
||||
() =>
|
||||
console.log(...message);
|
||||
const logged = (...message: any[]) => log(message);
|
||||
/**
|
||||
* a local function that returns either one value or the other,
|
||||
* depending on {t}'s CommandType. If the commandtype of
|
||||
* this module is CommandType.Both or CommandType.Text or CommandType.Slash,
|
||||
* return 'is', else return 'els'
|
||||
* @param t
|
||||
* @returns S | T
|
||||
*/
|
||||
const appCmd = <V extends CommandType, S, T>(t: V) => {
|
||||
return (is: S, els: T) => ((t & CommandType.Both) !== 0 ? is : els);
|
||||
};
|
||||
const curAppType = CommandTypeRaw[module.type];
|
||||
const createCommandData = () => {
|
||||
const cmd = appCmd(module.type);
|
||||
return {
|
||||
name: module.name,
|
||||
type: curAppType,
|
||||
description: cmd(module.description, ""),
|
||||
options: cmd(optionsTransformer((module as SlashCommand).options ?? []), []),
|
||||
defaultMemberPermissions,
|
||||
dmPermission,
|
||||
} as ApplicationCommandData;
|
||||
};
|
||||
|
||||
try {
|
||||
const commandData = createCommandData();
|
||||
try {
|
||||
const commandData = createCommandData();
|
||||
|
||||
if (!guildIds.length) {
|
||||
const cmd = (await client.application!.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (cmd) {
|
||||
if (!cmd.equals(commandData, true)) {
|
||||
logged(
|
||||
`Found differences in global command ${module.name}`,
|
||||
);
|
||||
cmd.edit(commandData).then(
|
||||
log(
|
||||
`${module.name} updated with new data successfully!`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return controller.next();
|
||||
}
|
||||
client
|
||||
.application!.commands.create(commandData)
|
||||
.then(log("Command created", module.name))
|
||||
.catch(c);
|
||||
return controller.next();
|
||||
}
|
||||
if (!guildIds.length) {
|
||||
const cmd = (await client.application!.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (cmd) {
|
||||
if (!cmd.equals(commandData, true)) {
|
||||
logged(`Found differences in global command ${module.name}`);
|
||||
cmd.edit(commandData).then(
|
||||
log(`${module.name} updated with new data successfully!`),
|
||||
);
|
||||
}
|
||||
return controller.next();
|
||||
}
|
||||
client
|
||||
.application!.commands.create(commandData)
|
||||
.then(log("Command created", module.name))
|
||||
.catch(c);
|
||||
return controller.next();
|
||||
}
|
||||
|
||||
for (const id of guildIds) {
|
||||
const guild = await client.guilds.fetch(id).catch(c);
|
||||
if (!guild) continue;
|
||||
const guildCmd = (await guild.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (guildCmd) {
|
||||
if (!guildCmd.equals(commandData, true)) {
|
||||
logged(`Found differences in command ${module.name}`);
|
||||
guildCmd
|
||||
.edit(commandData)
|
||||
.then(
|
||||
log(
|
||||
`${module.name} updated with new data successfully!`,
|
||||
),
|
||||
)
|
||||
.catch(c);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
guild.commands
|
||||
.create(commandData)
|
||||
.then(log("Guild Command created", module.name, guild.name))
|
||||
.catch(c);
|
||||
}
|
||||
return controller.next();
|
||||
} catch (e) {
|
||||
logged("Command did not register" + module.name);
|
||||
logged(e);
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
for (const id of guildIds) {
|
||||
const guild = await client.guilds.fetch(id).catch(c);
|
||||
if (!guild) continue;
|
||||
const guildCmd = (await guild.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (guildCmd) {
|
||||
if (!guildCmd.equals(commandData, true)) {
|
||||
logged(`Found differences in command ${module.name}`);
|
||||
guildCmd
|
||||
.edit(commandData)
|
||||
.then(log(`${module.name} updated with new data successfully!`))
|
||||
.catch(c);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
guild.commands
|
||||
.create(commandData)
|
||||
.then(log("Guild Command created", module.name, guild.name))
|
||||
.catch(c);
|
||||
}
|
||||
return controller.next();
|
||||
} catch (e) {
|
||||
logged("Command did not register" + module.name);
|
||||
logged(e);
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function optionsTransformer(ops: Array<SernOptionsData>) {
|
||||
return ops.map((el) => {
|
||||
switch (el.type) {
|
||||
case ApplicationCommandOptionType.String:
|
||||
case ApplicationCommandOptionType.Number:
|
||||
case ApplicationCommandOptionType.Integer: {
|
||||
return el.autocomplete && "command" in el
|
||||
? (({ command, ...el }) => el)(el)
|
||||
: el;
|
||||
}
|
||||
default:
|
||||
return el;
|
||||
}
|
||||
});
|
||||
return ops.map((el) => {
|
||||
switch (el.type) {
|
||||
case ApplicationCommandOptionType.String:
|
||||
case ApplicationCommandOptionType.Number:
|
||||
case ApplicationCommandOptionType.Integer: {
|
||||
return el.autocomplete && "command" in el ? (({ command, ...el }) => el)(el) : el;
|
||||
}
|
||||
default:
|
||||
return el;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export type NonEmptyArray<T extends `${number}` = `${number}`> = [T, ...T[]];
|
||||
|
||||
export interface ValidPublishOptions {
|
||||
guildIds: string[];
|
||||
dmPermission: boolean;
|
||||
defaultMemberPermissions: PermissionResolvable;
|
||||
guildIds: string[];
|
||||
dmPermission: boolean;
|
||||
defaultMemberPermissions: PermissionResolvable;
|
||||
}
|
||||
|
||||
interface GuildPublishOptions {
|
||||
guildIds?: NonEmptyArray;
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: never;
|
||||
guildIds?: NonEmptyArray;
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: never;
|
||||
}
|
||||
|
||||
interface GlobalPublishOptions {
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: false;
|
||||
guildIds?: never;
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: false;
|
||||
guildIds?: never;
|
||||
}
|
||||
|
||||
type BasePublishOptions = GuildPublishOptions | GlobalPublishOptions;
|
||||
|
||||
export type PublishOptions = BasePublishOptions &
|
||||
(
|
||||
| Required<Pick<BasePublishOptions, "defaultMemberPermissions">>
|
||||
| (
|
||||
| Required<Pick<BasePublishOptions, "dmPermission">>
|
||||
| Required<Pick<BasePublishOptions, "guildIds">>
|
||||
)
|
||||
);
|
||||
(
|
||||
| Required<Pick<BasePublishOptions, "defaultMemberPermissions">>
|
||||
| (
|
||||
| Required<Pick<BasePublishOptions, "dmPermission">>
|
||||
| Required<Pick<BasePublishOptions, "guildIds">>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { Presence } from '@sern/handler'
|
||||
import { ActivityType, ClientPresenceStatus } from 'discord.js';
|
||||
import { Presence } from "@sern/handler";
|
||||
import { ActivityType, ClientPresenceStatus } from "discord.js";
|
||||
|
||||
function shuffleArray<T>(array: T[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return [...array];
|
||||
}
|
||||
|
||||
const statuses =[[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"]] satisfies
|
||||
[ActivityType, string, ClientPresenceStatus][];
|
||||
const statuses = [
|
||||
[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"],
|
||||
] satisfies [ActivityType, string, ClientPresenceStatus][];
|
||||
|
||||
export default Presence.module({
|
||||
execute: () => {
|
||||
const [type, name, status] = statuses.at(0)!;
|
||||
return Presence
|
||||
.of({ activities: [ { type, name } ], status }) //start your presence with this.
|
||||
return Presence.of({ activities: [{ type, name }], status }) //start your presence with this.
|
||||
.repeated(() => {
|
||||
const [type, name, status] = [...shuffleArray(statuses)].shift()!;
|
||||
return {
|
||||
status,
|
||||
activities: [{ type, name }]
|
||||
activities: [{ type, name }],
|
||||
};
|
||||
}, 60_000); //repeat and setPresence with returned result every minute
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,53 +8,55 @@ import type { DocsChild } from "../../typings/docs.js";
|
||||
* Not bothering typing this json file
|
||||
*/
|
||||
export default class DocHandler {
|
||||
private __DocTrie!: TrieSearch<string, { name: string; node: DocsChild }>;
|
||||
private sectionTitleChildPairs: { name: string; node: DocsChild }[] = [];
|
||||
private sectionsOnly: string[] = [];
|
||||
get DocTrie() {
|
||||
return this.__DocTrie;
|
||||
}
|
||||
private __DocTrie!: TrieSearch<string, { name: string; node: DocsChild }>;
|
||||
private sectionTitleChildPairs: { name: string; node: DocsChild }[] = [];
|
||||
private sectionsOnly: string[] = [];
|
||||
get DocTrie() {
|
||||
return this.__DocTrie;
|
||||
}
|
||||
|
||||
private transformSections() {
|
||||
docs.groups.pop()!; //Removes "Functions" from json
|
||||
for (const section of docs.groups) {
|
||||
if (section.title === "Namespaces") {
|
||||
const first = docs.children.shift()!;
|
||||
//assumed that first element is Sern namespace. This helps speed up processing nodes
|
||||
this.sectionTitleChildPairs.push({
|
||||
name: "Namespaces",
|
||||
node: first as DocsChild,
|
||||
});
|
||||
while (first?.children?.length ?? 0 !== 0) {
|
||||
const cur = first.children?.pop()!;
|
||||
this.sectionTitleChildPairs.push({
|
||||
name: `Sern.${cur.name}`,
|
||||
node: cur as DocsChild,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const sectionChildNodes = section.children.map((id: string) => {
|
||||
const node = docs.children.find((c: { id: string }) => c.id === id)! as DocsChild;
|
||||
return {
|
||||
name: section.title,
|
||||
node,
|
||||
};
|
||||
});
|
||||
this.sectionsOnly.push(section.title);
|
||||
this.sectionTitleChildPairs.push(...sectionChildNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
private transformSections() {
|
||||
docs.groups.pop()!; //Removes "Functions" from json
|
||||
for (const section of docs.groups) {
|
||||
if (section.title === "Namespaces") {
|
||||
const first = docs.children.shift()!;
|
||||
//assumed that first element is Sern namespace. This helps speed up processing nodes
|
||||
this.sectionTitleChildPairs.push({
|
||||
name: "Namespaces",
|
||||
node: first as DocsChild,
|
||||
});
|
||||
while (first?.children?.length ?? 0 !== 0) {
|
||||
const cur = first.children?.pop()!;
|
||||
this.sectionTitleChildPairs.push({
|
||||
name: `Sern.${cur.name}`,
|
||||
node: cur as DocsChild,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const sectionChildNodes = section.children.map((id: string) => {
|
||||
const node = docs.children.find(
|
||||
(c: { id: string }) => c.id === id,
|
||||
)! as DocsChild;
|
||||
return {
|
||||
name: section.title,
|
||||
node,
|
||||
};
|
||||
});
|
||||
this.sectionsOnly.push(section.title);
|
||||
this.sectionTitleChildPairs.push(...sectionChildNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.transformSections();
|
||||
const trie = new TrieSearch<string, { name: string; node: DocsChild }>([
|
||||
"name",
|
||||
["node", "kindString"],
|
||||
["node", "id"],
|
||||
["node", "name"],
|
||||
]);
|
||||
trie.addAll(this.sectionTitleChildPairs);
|
||||
this.__DocTrie = trie;
|
||||
}
|
||||
setup() {
|
||||
this.transformSections();
|
||||
const trie = new TrieSearch<string, { name: string; node: DocsChild }>([
|
||||
"name",
|
||||
["node", "kindString"],
|
||||
["node", "id"],
|
||||
["node", "name"],
|
||||
]);
|
||||
trie.addAll(this.sectionTitleChildPairs);
|
||||
this.__DocTrie = trie;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,51 +3,56 @@ import { findBestMatch } from "string-similarity";
|
||||
import type { TagData } from "typings";
|
||||
|
||||
export class FuzzyMatcher {
|
||||
public constructor(private readonly message: Message, private readonly tags: TagData[]) {}
|
||||
public constructor(
|
||||
private readonly message: Message,
|
||||
private readonly tags: TagData[],
|
||||
) {}
|
||||
|
||||
readonly #regex = /<@!?(?<id>\d{17,20})>/g;
|
||||
readonly #regex = /<@!?(?<id>\d{17,20})>/g;
|
||||
|
||||
private get cleanContent(): string {
|
||||
return this.message.content.replace(this.#regex, "");
|
||||
}
|
||||
private get cleanContent(): string {
|
||||
return this.message.content.replace(this.#regex, "");
|
||||
}
|
||||
|
||||
public get mentionedUser(): User | undefined {
|
||||
return this.message.mentions.users.first();
|
||||
}
|
||||
public get mentionedUser(): User | undefined {
|
||||
return this.message.mentions.users.first();
|
||||
}
|
||||
|
||||
public fuzzyMatch() {
|
||||
const keywords = this.tags.flatMap((t) => t.keywords).map((k) => k.toLowerCase());
|
||||
const matches = findBestMatch(this.cleanContent.toLowerCase(), keywords);
|
||||
public fuzzyMatch() {
|
||||
const keywords = this.tags.flatMap((t) => t.keywords).map((k) => k.toLowerCase());
|
||||
const matches = findBestMatch(this.cleanContent.toLowerCase(), keywords);
|
||||
|
||||
if (matches.bestMatch.rating < 0.4) return null;
|
||||
const words = this.cleanContent.toLowerCase().split(" ");
|
||||
if (matches.bestMatch.rating < 0.4) return null;
|
||||
const words = this.cleanContent.toLowerCase().split(" ");
|
||||
|
||||
const firstMatchedTag = this.tags.find((t) => t.keywords.includes(matches.bestMatch.target));
|
||||
if (!firstMatchedTag) return null;
|
||||
const firstMatchedTag = this.tags.find((t) =>
|
||||
t.keywords.includes(matches.bestMatch.target),
|
||||
);
|
||||
if (!firstMatchedTag) return null;
|
||||
|
||||
const keyword = firstMatchedTag.keywords.find((k) => {
|
||||
return k === matches.bestMatch.target;
|
||||
});
|
||||
const keyword = firstMatchedTag.keywords.find((k) => {
|
||||
return k === matches.bestMatch.target;
|
||||
});
|
||||
|
||||
if (!keyword) return null;
|
||||
const splitted = keyword.toLowerCase().split(" ");
|
||||
if (!keyword) return null;
|
||||
const splitted = keyword.toLowerCase().split(" ");
|
||||
|
||||
const Confidence = this.checkArray(words, splitted);
|
||||
const Confidence = this.checkArray(words, splitted);
|
||||
|
||||
const final = (Confidence * 2 + matches.bestMatch.rating) / 3; // weighted average
|
||||
return {
|
||||
tag: firstMatchedTag,
|
||||
confidence: final,
|
||||
};
|
||||
}
|
||||
const final = (Confidence * 2 + matches.bestMatch.rating) / 3; // weighted average
|
||||
return {
|
||||
tag: firstMatchedTag,
|
||||
confidence: final,
|
||||
};
|
||||
}
|
||||
|
||||
private checkArray(toCheck: string[], toMatch: string[]) {
|
||||
const booleans: boolean[] = [];
|
||||
for (const word of toMatch) {
|
||||
const check = findBestMatch(word, toCheck);
|
||||
if (check.bestMatch.rating > 0.85) booleans.push(true);
|
||||
else booleans.push(false);
|
||||
}
|
||||
return booleans.filter(Boolean).length / booleans.length;
|
||||
}
|
||||
private checkArray(toCheck: string[], toMatch: string[]) {
|
||||
const booleans: boolean[] = [];
|
||||
for (const word of toMatch) {
|
||||
const check = findBestMatch(word, toCheck);
|
||||
if (check.bestMatch.rating > 0.85) booleans.push(true);
|
||||
else booleans.push(false);
|
||||
}
|
||||
return booleans.filter(Boolean).length / booleans.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,37 @@ import type { Logging, LogPayload } from "@sern/handler";
|
||||
import winston from "winston";
|
||||
import util from "util";
|
||||
export class SernLogger implements Logging {
|
||||
private _winston!: winston.Logger;
|
||||
private _winston!: winston.Logger;
|
||||
|
||||
public constructor(level: string, isProd = false) {
|
||||
this._winston = winston.createLogger({
|
||||
level,
|
||||
format: winston.format.json(),
|
||||
});
|
||||
if (!isProd) {
|
||||
this._winston.add(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.simple(),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this._winston.add(new winston.transports.File({ filename: "error.log" }));
|
||||
}
|
||||
}
|
||||
public constructor(level: string, isProd = false) {
|
||||
this._winston = winston.createLogger({
|
||||
level,
|
||||
format: winston.format.json(),
|
||||
});
|
||||
if (!isProd) {
|
||||
this._winston.add(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.simple(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this._winston.add(new winston.transports.File({ filename: "error.log" }));
|
||||
}
|
||||
}
|
||||
|
||||
public error(payload: LogPayload<unknown>): void {
|
||||
this._winston.error(payload.message);
|
||||
}
|
||||
public error(payload: LogPayload<unknown>): void {
|
||||
this._winston.error(payload.message);
|
||||
}
|
||||
|
||||
public warning(payload: LogPayload<unknown>): void {
|
||||
this._winston.warn(util.format(payload.message));
|
||||
}
|
||||
public warning(payload: LogPayload<unknown>): void {
|
||||
this._winston.warn(util.format(payload.message));
|
||||
}
|
||||
|
||||
public info(payload: LogPayload<unknown>): void {
|
||||
this._winston.info(payload.message);
|
||||
}
|
||||
public info(payload: LogPayload<unknown>): void {
|
||||
this._winston.info(payload.message);
|
||||
}
|
||||
|
||||
public debug(payload: LogPayload<unknown>): void {
|
||||
this._winston.debug(payload.message);
|
||||
}
|
||||
public debug(payload: LogPayload<unknown>): void {
|
||||
this._winston.debug(payload.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,307 +1,308 @@
|
||||
import {
|
||||
ActionRow,
|
||||
ActionRowBuilder,
|
||||
APISelectMenuOption,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
CommandInteraction,
|
||||
EmbedBuilder,
|
||||
Message,
|
||||
MessageActionRowComponent,
|
||||
RestOrArray,
|
||||
StringSelectMenuBuilder,
|
||||
SelectMenuComponentOptionData,
|
||||
SelectMenuOptionBuilder,
|
||||
User,
|
||||
APIStringSelectComponent,
|
||||
ActionRow,
|
||||
ActionRowBuilder,
|
||||
APISelectMenuOption,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
CommandInteraction,
|
||||
EmbedBuilder,
|
||||
Message,
|
||||
MessageActionRowComponent,
|
||||
RestOrArray,
|
||||
StringSelectMenuBuilder,
|
||||
SelectMenuComponentOptionData,
|
||||
SelectMenuOptionBuilder,
|
||||
User,
|
||||
APIStringSelectComponent,
|
||||
} from "discord.js";
|
||||
|
||||
export class Paginator {
|
||||
private currentCount: number = 0;
|
||||
private selectMenuOptions?: RestOrArray<
|
||||
SelectMenuOptionBuilder | APISelectMenuOption | SelectMenuComponentOptionData
|
||||
>;
|
||||
private descriptions?: string[];
|
||||
private currentCount: number = 0;
|
||||
private selectMenuOptions?: RestOrArray<
|
||||
SelectMenuOptionBuilder | APISelectMenuOption | SelectMenuComponentOptionData
|
||||
>;
|
||||
private descriptions?: string[];
|
||||
|
||||
public get pages() {
|
||||
return (this.options.embeds?.length ?? this.descriptions?.length)!;
|
||||
}
|
||||
public get pages() {
|
||||
return (this.options.embeds?.length ?? this.descriptions?.length)!;
|
||||
}
|
||||
|
||||
public constructor(private readonly options: PaginatorOptions = {}) {
|
||||
this.options.emojis ??= ["⏮", "◀", "⏹", "▶", "⏭"];
|
||||
this.options.embeds &&= this.options.embeds.map((embed, i) =>
|
||||
new EmbedBuilder(embed.data).setFooter({
|
||||
text: `Page ${i + 1}/${this.options.embeds!.length}`,
|
||||
})
|
||||
);
|
||||
public constructor(private readonly options: PaginatorOptions = {}) {
|
||||
this.options.emojis ??= ["⏮", "◀", "⏹", "▶", "⏭"];
|
||||
this.options.embeds &&= this.options.embeds.map((embed, i) =>
|
||||
new EmbedBuilder(embed.data).setFooter({
|
||||
text: `Page ${i + 1}/${this.options.embeds!.length}`,
|
||||
}),
|
||||
);
|
||||
|
||||
if (this.pages > 25) this.options.includeSelectMenu = false;
|
||||
}
|
||||
if (this.pages > 25) this.options.includeSelectMenu = false;
|
||||
}
|
||||
|
||||
public setEmbeds(embeds: EmbedBuilder[]): this {
|
||||
this.options.embeds = embeds;
|
||||
return this;
|
||||
}
|
||||
public setEmbeds(embeds: EmbedBuilder[]): this {
|
||||
this.options.embeds = embeds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setDescriptions(descriptions: string[]): this {
|
||||
this.descriptions = descriptions;
|
||||
return this;
|
||||
}
|
||||
public setDescriptions(descriptions: string[]): this {
|
||||
this.descriptions = descriptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setCurrentCount(count: number): this {
|
||||
this.currentCount = --count;
|
||||
return this;
|
||||
}
|
||||
public setCurrentCount(count: number): this {
|
||||
this.currentCount = --count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setSelectMenuOptions(
|
||||
...options: RestOrArray<
|
||||
SelectMenuOptionBuilder | APISelectMenuOption | SelectMenuComponentOptionData
|
||||
>
|
||||
): this {
|
||||
this.selectMenuOptions = options;
|
||||
return this;
|
||||
}
|
||||
public setSelectMenuOptions(
|
||||
...options: RestOrArray<
|
||||
SelectMenuOptionBuilder | APISelectMenuOption | SelectMenuComponentOptionData
|
||||
>
|
||||
): this {
|
||||
this.selectMenuOptions = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public async run(messageOrInteraction: Message | CommandInteraction, user?: User) {
|
||||
this.sanityChecks();
|
||||
public async run(messageOrInteraction: Message | CommandInteraction, user?: User) {
|
||||
this.sanityChecks();
|
||||
|
||||
const target = user
|
||||
? user
|
||||
: messageOrInteraction instanceof Message
|
||||
? messageOrInteraction.author
|
||||
: messageOrInteraction.user;
|
||||
const target = user
|
||||
? user
|
||||
: messageOrInteraction instanceof Message
|
||||
? messageOrInteraction.author
|
||||
: messageOrInteraction.user;
|
||||
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
|
||||
const rows = Boolean(this.buildSelect())
|
||||
? [this.buildButtons(), this.buildSelect()!]
|
||||
: [this.buildButtons()];
|
||||
const rows = Boolean(this.buildSelect())
|
||||
? [this.buildButtons(), this.buildSelect()!]
|
||||
: [this.buildButtons()];
|
||||
|
||||
if (messageOrInteraction instanceof Message) {
|
||||
const message = await this.handleMessage(messageOrInteraction, embeds, rows);
|
||||
if (messageOrInteraction instanceof Message) {
|
||||
const message = await this.handleMessage(messageOrInteraction, embeds, rows);
|
||||
|
||||
return this.handleCollector(message, target);
|
||||
} else {
|
||||
const message = await this.handleInteraction(messageOrInteraction, embeds, rows);
|
||||
return this.handleCollector(message, target);
|
||||
}
|
||||
}
|
||||
return this.handleCollector(message, target);
|
||||
} else {
|
||||
const message = await this.handleInteraction(messageOrInteraction, embeds, rows);
|
||||
return this.handleCollector(message, target);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleMessage(
|
||||
message: Message,
|
||||
embeds: EmbedBuilder[],
|
||||
rows: (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[]
|
||||
) {
|
||||
const msg = await message.channel.send({
|
||||
embeds: [embeds![this.currentCount]],
|
||||
components: rows,
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
private async handleMessage(
|
||||
message: Message,
|
||||
embeds: EmbedBuilder[],
|
||||
rows: (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[],
|
||||
) {
|
||||
const msg = await message.channel.send({
|
||||
embeds: [embeds![this.currentCount]],
|
||||
components: rows,
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
private async handleInteraction(
|
||||
interaction: CommandInteraction,
|
||||
embeds: EmbedBuilder[],
|
||||
rows: (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[]
|
||||
) {
|
||||
let msg: Message<boolean>;
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
msg = await interaction.editReply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: rows,
|
||||
});
|
||||
} else
|
||||
msg = await interaction.reply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: rows,
|
||||
fetchReply: true,
|
||||
ephemeral: Boolean(this.options.ephemeral),
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
private async handleInteraction(
|
||||
interaction: CommandInteraction,
|
||||
embeds: EmbedBuilder[],
|
||||
rows: (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[],
|
||||
) {
|
||||
let msg: Message<boolean>;
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
msg = await interaction.editReply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: rows,
|
||||
});
|
||||
} else
|
||||
msg = await interaction.reply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: rows,
|
||||
fetchReply: true,
|
||||
ephemeral: Boolean(this.options.ephemeral),
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
private handleCollector(message: Message, user: User) {
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
const collector = message.createMessageComponentCollector({
|
||||
time: this.options.time ?? 6_00_000,
|
||||
filter: (i) => i.user.id === user.id,
|
||||
});
|
||||
private handleCollector(message: Message, user: User) {
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
const collector = message.createMessageComponentCollector({
|
||||
time: this.options.time ?? 6_00_000,
|
||||
filter: (i) => i.user.id === user.id,
|
||||
});
|
||||
|
||||
collector.on("collect", async (i) => {
|
||||
collector.resetTimer();
|
||||
collector.on("collect", async (i) => {
|
||||
collector.resetTimer();
|
||||
|
||||
switch (i.customId as ButtonIds) {
|
||||
case "@paginator/first":
|
||||
this.currentCount = 0;
|
||||
break;
|
||||
case "@paginator/back":
|
||||
this.currentCount--;
|
||||
break;
|
||||
case "@paginator/stop":
|
||||
i.message.components = [];
|
||||
break;
|
||||
case "@paginator/forward":
|
||||
this.currentCount++;
|
||||
break;
|
||||
case "@paginator/last":
|
||||
this.currentCount = this.pages - 1;
|
||||
break;
|
||||
default:
|
||||
if (!i.isSelectMenu()) return;
|
||||
this.currentCount = parseInt(i.values[0]);
|
||||
}
|
||||
switch (i.customId as ButtonIds) {
|
||||
case "@paginator/first":
|
||||
this.currentCount = 0;
|
||||
break;
|
||||
case "@paginator/back":
|
||||
this.currentCount--;
|
||||
break;
|
||||
case "@paginator/stop":
|
||||
i.message.components = [];
|
||||
break;
|
||||
case "@paginator/forward":
|
||||
this.currentCount++;
|
||||
break;
|
||||
case "@paginator/last":
|
||||
this.currentCount = this.pages - 1;
|
||||
break;
|
||||
default:
|
||||
if (!i.isSelectMenu()) return;
|
||||
this.currentCount = parseInt(i.values[0]);
|
||||
}
|
||||
|
||||
if (this.currentCount < 0) this.currentCount = 0;
|
||||
if (this.currentCount >= this.pages) this.currentCount = this.pages - 1;
|
||||
if (this.currentCount < 0) this.currentCount = 0;
|
||||
if (this.currentCount >= this.pages) this.currentCount = this.pages - 1;
|
||||
|
||||
await i.update({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: i.message.components.length
|
||||
? this.buildSelect()
|
||||
? [this.buildButtons(), this.updateSelect(i.message.components)[1]]
|
||||
: [this.buildButtons()]
|
||||
: [],
|
||||
});
|
||||
await i.update({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components: i.message.components.length
|
||||
? this.buildSelect()
|
||||
? [this.buildButtons(), this.updateSelect(i.message.components)[1]]
|
||||
: [this.buildButtons()]
|
||||
: [],
|
||||
});
|
||||
|
||||
if (i.message.components.length === 0) collector.stop();
|
||||
});
|
||||
if (i.message.components.length === 0) collector.stop();
|
||||
});
|
||||
|
||||
collector.on("ignore", async (i) => {
|
||||
if (!this.options.wrongInteractionResponse) {
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
const components = Boolean(this.buildSelect())
|
||||
? [this.buildButtons(), this.buildSelect()!]
|
||||
: [this.buildButtons()];
|
||||
collector.on("ignore", async (i) => {
|
||||
if (!this.options.wrongInteractionResponse) {
|
||||
const embeds = this.options.embeds ?? this.buildEmbeds()!;
|
||||
const components = Boolean(this.buildSelect())
|
||||
? [this.buildButtons(), this.buildSelect()!]
|
||||
: [this.buildButtons()];
|
||||
|
||||
const msg = await i.reply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
const msg = await i.reply({
|
||||
embeds: [embeds[this.currentCount]],
|
||||
components,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
|
||||
return this.handleCollector(msg, i.user);
|
||||
}
|
||||
await i.reply({
|
||||
content: this.options.wrongInteractionResponse,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
return this.handleCollector(msg, i.user);
|
||||
}
|
||||
await i.reply({
|
||||
content: this.options.wrongInteractionResponse,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
collector.on("end", async () => {
|
||||
await message.edit({ components: [] }).catch(() => null);
|
||||
});
|
||||
}
|
||||
collector.on("end", async () => {
|
||||
await message.edit({ components: [] }).catch(() => null);
|
||||
});
|
||||
}
|
||||
|
||||
private buildButtons() {
|
||||
const embeds = (this.options.embeds ?? this.descriptions)!;
|
||||
const buttons = [];
|
||||
const first = 0;
|
||||
const last = this.pages - 1;
|
||||
const ids = ["first", "back", "stop", "forward", "last"];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId(`@paginator/${ids[i]}`)
|
||||
.setEmoji(this.options.emojis![i])
|
||||
.setDisabled(
|
||||
embeds.length === 1 ||
|
||||
((i === 0 || i === 1) && first === this.currentCount) ||
|
||||
((i === 3 || i === 4) && last === this.currentCount)
|
||||
)
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
buttons.push(button);
|
||||
}
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().setComponents(buttons);
|
||||
return row;
|
||||
}
|
||||
private buildButtons() {
|
||||
const embeds = (this.options.embeds ?? this.descriptions)!;
|
||||
const buttons = [];
|
||||
const first = 0;
|
||||
const last = this.pages - 1;
|
||||
const ids = ["first", "back", "stop", "forward", "last"];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId(`@paginator/${ids[i]}`)
|
||||
.setEmoji(this.options.emojis![i])
|
||||
.setDisabled(
|
||||
embeds.length === 1 ||
|
||||
((i === 0 || i === 1) && first === this.currentCount) ||
|
||||
((i === 3 || i === 4) && last === this.currentCount),
|
||||
)
|
||||
.setStyle(ButtonStyle.Secondary);
|
||||
buttons.push(button);
|
||||
}
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().setComponents(buttons);
|
||||
return row;
|
||||
}
|
||||
|
||||
private buildSelect() {
|
||||
if (this.options.includeSelectMenu === false) return;
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId("@paginator/select")
|
||||
.setMaxValues(1)
|
||||
.setMinValues(1)
|
||||
.setDisabled(this.pages === 1)
|
||||
.setPlaceholder(`Navigate to page`)
|
||||
.setOptions(
|
||||
...(this.selectMenuOptions ??
|
||||
Array(this.pages)
|
||||
.fill(null)
|
||||
.map((_, i) => ({
|
||||
label: `Page ${i + 1}`,
|
||||
value: `${i}`,
|
||||
default: i === this.currentCount,
|
||||
})))
|
||||
);
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(select);
|
||||
return row;
|
||||
}
|
||||
private buildSelect() {
|
||||
if (this.options.includeSelectMenu === false) return;
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId("@paginator/select")
|
||||
.setMaxValues(1)
|
||||
.setMinValues(1)
|
||||
.setDisabled(this.pages === 1)
|
||||
.setPlaceholder(`Navigate to page`)
|
||||
.setOptions(
|
||||
...(this.selectMenuOptions ??
|
||||
Array(this.pages)
|
||||
.fill(null)
|
||||
.map((_, i) => ({
|
||||
label: `Page ${i + 1}`,
|
||||
value: `${i}`,
|
||||
default: i === this.currentCount,
|
||||
}))),
|
||||
);
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(select);
|
||||
return row;
|
||||
}
|
||||
|
||||
private buildEmbeds() {
|
||||
if (!this.descriptions) return;
|
||||
const defaultEmbed = new EmbedBuilder();
|
||||
const template = this.options.template ?? defaultEmbed;
|
||||
const embeds = Array(this.pages)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
const embed = new EmbedBuilder(template.data);
|
||||
embed.setDescription(this.descriptions![i]);
|
||||
!embed.data.color && embed.setColor("Random");
|
||||
embed.setFooter({
|
||||
text: `Page ${i + 1}/${this.descriptions!.length}`,
|
||||
});
|
||||
return embed;
|
||||
});
|
||||
return embeds;
|
||||
}
|
||||
private buildEmbeds() {
|
||||
if (!this.descriptions) return;
|
||||
const defaultEmbed = new EmbedBuilder();
|
||||
const template = this.options.template ?? defaultEmbed;
|
||||
const embeds = Array(this.pages)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
const embed = new EmbedBuilder(template.data);
|
||||
embed.setDescription(this.descriptions![i]);
|
||||
!embed.data.color && embed.setColor("Random");
|
||||
embed.setFooter({
|
||||
text: `Page ${i + 1}/${this.descriptions!.length}`,
|
||||
});
|
||||
return embed;
|
||||
});
|
||||
return embeds;
|
||||
}
|
||||
|
||||
private updateSelect(components: ActionRow<MessageActionRowComponent>[]) {
|
||||
const selectMenuOption = (components[1].components[0].data as APIStringSelectComponent).options;
|
||||
for (const option of selectMenuOption) {
|
||||
if (option.value === `${this.currentCount}`) option.default = true;
|
||||
else option.default = false;
|
||||
}
|
||||
return components;
|
||||
}
|
||||
private updateSelect(components: ActionRow<MessageActionRowComponent>[]) {
|
||||
const selectMenuOption = (components[1].components[0].data as APIStringSelectComponent)
|
||||
.options;
|
||||
for (const option of selectMenuOption) {
|
||||
if (option.value === `${this.currentCount}`) option.default = true;
|
||||
else option.default = false;
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
private sanityChecks() {
|
||||
if (!this.options.embeds && !this.descriptions) {
|
||||
throw new Error("No embeds or descriptions provided");
|
||||
}
|
||||
if (this.options.embeds && this.options.template) {
|
||||
throw new Error("Cannot provide both embeds and template");
|
||||
}
|
||||
if (this.options.embeds && !this.options.embeds.length) {
|
||||
throw new Error("No embeds provided");
|
||||
}
|
||||
if (this.descriptions && !this.descriptions.length) {
|
||||
throw new Error("No descriptions provided");
|
||||
}
|
||||
if (this.options.template && !this.descriptions?.length) {
|
||||
throw new Error("No descriptions provided");
|
||||
}
|
||||
if (
|
||||
this.options.includeSelectMenu &&
|
||||
(this.options.embeds?.length! > 25 || this.descriptions?.length! > 25)
|
||||
) {
|
||||
throw new Error("Too many pages to include select menu");
|
||||
}
|
||||
}
|
||||
private sanityChecks() {
|
||||
if (!this.options.embeds && !this.descriptions) {
|
||||
throw new Error("No embeds or descriptions provided");
|
||||
}
|
||||
if (this.options.embeds && this.options.template) {
|
||||
throw new Error("Cannot provide both embeds and template");
|
||||
}
|
||||
if (this.options.embeds && !this.options.embeds.length) {
|
||||
throw new Error("No embeds provided");
|
||||
}
|
||||
if (this.descriptions && !this.descriptions.length) {
|
||||
throw new Error("No descriptions provided");
|
||||
}
|
||||
if (this.options.template && !this.descriptions?.length) {
|
||||
throw new Error("No descriptions provided");
|
||||
}
|
||||
if (
|
||||
this.options.includeSelectMenu &&
|
||||
(this.options.embeds?.length! > 25 || this.descriptions?.length! > 25)
|
||||
) {
|
||||
throw new Error("Too many pages to include select menu");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface PaginatorOptions {
|
||||
time?: number;
|
||||
embeds?: EmbedBuilder[];
|
||||
template?: EmbedBuilder;
|
||||
includeSelectMenu?: boolean;
|
||||
emojis?: [string, string, string, string, string];
|
||||
wrongInteractionResponse?: string;
|
||||
ephemeral?: boolean;
|
||||
time?: number;
|
||||
embeds?: EmbedBuilder[];
|
||||
template?: EmbedBuilder;
|
||||
includeSelectMenu?: boolean;
|
||||
emojis?: [string, string, string, string, string];
|
||||
wrongInteractionResponse?: string;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
||||
type ButtonIds =
|
||||
| "@paginator/first"
|
||||
| "@paginator/back"
|
||||
| "@paginator/stop"
|
||||
| "@paginator/forward"
|
||||
| "@paginator/last";
|
||||
| "@paginator/first"
|
||||
| "@paginator/back"
|
||||
| "@paginator/stop"
|
||||
| "@paginator/forward"
|
||||
| "@paginator/last";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Snowflake } from "discord-api-types/v10";
|
||||
import {
|
||||
Collection,
|
||||
CommandInteraction,
|
||||
GuildBasedChannel,
|
||||
GuildMember,
|
||||
Role,
|
||||
User,
|
||||
Collection,
|
||||
CommandInteraction,
|
||||
GuildBasedChannel,
|
||||
GuildMember,
|
||||
Role,
|
||||
User,
|
||||
} from "discord.js";
|
||||
|
||||
/**
|
||||
@@ -17,85 +17,85 @@ import {
|
||||
* ```
|
||||
*/
|
||||
export class Resolver {
|
||||
public constructor(
|
||||
private readonly content: string,
|
||||
private readonly interaction: CommandInteraction
|
||||
) {}
|
||||
public constructor(
|
||||
private readonly content: string,
|
||||
private readonly interaction: CommandInteraction,
|
||||
) {}
|
||||
|
||||
readonly #regex = {
|
||||
Channel: /<#(?<id>\d{17,20})>/g,
|
||||
Role: /<@&(?<id>\d{17,20})>/g,
|
||||
User: /<@!?(?<id>\d{17,20})>/g,
|
||||
};
|
||||
readonly #regex = {
|
||||
Channel: /<#(?<id>\d{17,20})>/g,
|
||||
Role: /<@&(?<id>\d{17,20})>/g,
|
||||
User: /<@!?(?<id>\d{17,20})>/g,
|
||||
};
|
||||
|
||||
private getIds(mentionType: "Channel" | "Role" | "User"): string[] {
|
||||
const matches = this.content.matchAll(this.#regex[mentionType]);
|
||||
return Array.from(matches)
|
||||
.map((match) => match.groups?.id)
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
private getIds(mentionType: "Channel" | "Role" | "User"): string[] {
|
||||
const matches = this.content.matchAll(this.#regex[mentionType]);
|
||||
return Array.from(matches)
|
||||
.map((match) => match.groups?.id)
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a user from the content.
|
||||
* @returns The collection of resolved {@link User users}.
|
||||
*/
|
||||
public get users(): Readonly<Collection<Snowflake, User>> {
|
||||
const users = this.getIds("User")
|
||||
.map((id) => this.interaction.client.users.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((u) => [u!.id, u]) as [Snowflake, User][];
|
||||
/**
|
||||
* Resolves a user from the content.
|
||||
* @returns The collection of resolved {@link User users}.
|
||||
*/
|
||||
public get users(): Readonly<Collection<Snowflake, User>> {
|
||||
const users = this.getIds("User")
|
||||
.map((id) => this.interaction.client.users.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((u) => [u!.id, u]) as [Snowflake, User][];
|
||||
|
||||
return new Collection<Snowflake, User>(users);
|
||||
}
|
||||
return new Collection<Snowflake, User>(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a member from the content.
|
||||
* @returns The collection of resolved {@link GuildMember members}.
|
||||
*/
|
||||
public get members(): Readonly<Collection<Snowflake, GuildMember>> {
|
||||
const members = this.getIds("User")
|
||||
.map((id) => this.interaction.guild?.members.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((m) => [m!.id, m]) as [Snowflake, GuildMember][];
|
||||
/**
|
||||
* Resolves a member from the content.
|
||||
* @returns The collection of resolved {@link GuildMember members}.
|
||||
*/
|
||||
public get members(): Readonly<Collection<Snowflake, GuildMember>> {
|
||||
const members = this.getIds("User")
|
||||
.map((id) => this.interaction.guild?.members.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((m) => [m!.id, m]) as [Snowflake, GuildMember][];
|
||||
|
||||
return new Collection<Snowflake, GuildMember>(members);
|
||||
}
|
||||
return new Collection<Snowflake, GuildMember>(members);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a channel from the content.
|
||||
* @returns The collection of resolved {@link GuildBasedChannel channels}.
|
||||
*/
|
||||
public get channels(): Readonly<Collection<Snowflake, GuildBasedChannel>> {
|
||||
const channels = this.getIds("Channel")
|
||||
.map((id) => this.interaction.guild?.channels.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((c) => [c!.id, c]) as [Snowflake, GuildBasedChannel][];
|
||||
/**
|
||||
* Resolves a channel from the content.
|
||||
* @returns The collection of resolved {@link GuildBasedChannel channels}.
|
||||
*/
|
||||
public get channels(): Readonly<Collection<Snowflake, GuildBasedChannel>> {
|
||||
const channels = this.getIds("Channel")
|
||||
.map((id) => this.interaction.guild?.channels.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((c) => [c!.id, c]) as [Snowflake, GuildBasedChannel][];
|
||||
|
||||
return new Collection<Snowflake, GuildBasedChannel>(channels);
|
||||
}
|
||||
return new Collection<Snowflake, GuildBasedChannel>(channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a role from the content.
|
||||
* @returns The collection of resolved {@link Role roles}.
|
||||
*/
|
||||
public get roles(): Readonly<Collection<Snowflake, Role>> {
|
||||
const roles = this.getIds("Role")
|
||||
.map((id) => this.interaction.guild?.roles.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((r) => [r!.id, r]) as [Snowflake, Role][];
|
||||
/**
|
||||
* Resolves a role from the content.
|
||||
* @returns The collection of resolved {@link Role roles}.
|
||||
*/
|
||||
public get roles(): Readonly<Collection<Snowflake, Role>> {
|
||||
const roles = this.getIds("Role")
|
||||
.map((id) => this.interaction.guild?.roles.cache.get(id))
|
||||
.filter(Boolean)
|
||||
.map((r) => [r!.id, r]) as [Snowflake, Role][];
|
||||
|
||||
return new Collection<Snowflake, Role>(roles);
|
||||
}
|
||||
return new Collection<Snowflake, Role>(roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a URL from the content
|
||||
* @retunrns The url
|
||||
*/
|
||||
public get url(): Readonly<URL> | null {
|
||||
try {
|
||||
return new URL(this.content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolves a URL from the content
|
||||
* @retunrns The url
|
||||
*/
|
||||
public get url(): Readonly<URL> | null {
|
||||
try {
|
||||
return new URL(this.content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,215 +1,217 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
APIButtonComponentWithCustomId,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
CacheType,
|
||||
ChatInputCommandInteraction,
|
||||
ComponentType,
|
||||
GuildMember,
|
||||
InteractionCollector,
|
||||
InteractionResponse,
|
||||
User,
|
||||
ActionRowBuilder,
|
||||
APIButtonComponentWithCustomId,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
CacheType,
|
||||
ChatInputCommandInteraction,
|
||||
ComponentType,
|
||||
GuildMember,
|
||||
InteractionCollector,
|
||||
InteractionResponse,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import { Timestamp } from "./Timestamp.js";
|
||||
|
||||
export class TicTacToe {
|
||||
public constructor(public readonly time?: number) {
|
||||
this.time ??= 60_000;
|
||||
}
|
||||
public constructor(public readonly time?: number) {
|
||||
this.time ??= 60_000;
|
||||
}
|
||||
|
||||
readonly #X = `<:TTTX:879637390908620831>`;
|
||||
readonly #O = `<:TTTO:879637486492594217>`;
|
||||
readonly #Empty = `<:thevoid:986649133110685726>`;
|
||||
readonly #WinConditions: Combination[][] = [
|
||||
["1-1", "1-2", "1-3"],
|
||||
["2-1", "2-2", "2-3"],
|
||||
["3-1", "3-2", "3-3"],
|
||||
["1-1", "2-1", "3-1"],
|
||||
["1-2", "2-2", "3-2"],
|
||||
["1-3", "2-3", "3-3"],
|
||||
["1-1", "2-2", "3-3"],
|
||||
["1-3", "2-2", "3-1"],
|
||||
];
|
||||
readonly #X = `<:TTTX:879637390908620831>`;
|
||||
readonly #O = `<:TTTO:879637486492594217>`;
|
||||
readonly #Empty = `<:thevoid:986649133110685726>`;
|
||||
readonly #WinConditions: Combination[][] = [
|
||||
["1-1", "1-2", "1-3"],
|
||||
["2-1", "2-2", "2-3"],
|
||||
["3-1", "3-2", "3-3"],
|
||||
["1-1", "2-1", "3-1"],
|
||||
["1-2", "2-2", "3-2"],
|
||||
["1-3", "2-3", "3-3"],
|
||||
["1-1", "2-2", "3-3"],
|
||||
["1-3", "2-2", "3-1"],
|
||||
];
|
||||
|
||||
public sanityChecks(interaction: ChatInputCommandInteraction) {
|
||||
const member = interaction.options.getMember("opponent") as GuildMember;
|
||||
if (!member) return void interaction.reply("Member not in server!");
|
||||
const { user } = member;
|
||||
if (user.bot) return void interaction.reply(`Can't play with bots bruh`);
|
||||
if (user.id === interaction.user.id)
|
||||
return void interaction.reply(`Don't dare to play with yourself idiot`);
|
||||
return user;
|
||||
}
|
||||
public sanityChecks(interaction: ChatInputCommandInteraction) {
|
||||
const member = interaction.options.getMember("opponent") as GuildMember;
|
||||
if (!member) return void interaction.reply("Member not in server!");
|
||||
const { user } = member;
|
||||
if (user.bot) return void interaction.reply(`Can't play with bots bruh`);
|
||||
if (user.id === interaction.user.id)
|
||||
return void interaction.reply(`Don't dare to play with yourself idiot`);
|
||||
return user;
|
||||
}
|
||||
|
||||
public createCollector(response: InteractionResponse<boolean>, player: User, opponent: User) {
|
||||
return response.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => [player.id, opponent.id].includes(i.user.id),
|
||||
time: this.time,
|
||||
});
|
||||
}
|
||||
public createCollector(response: InteractionResponse<boolean>, player: User, opponent: User) {
|
||||
return response.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => [player.id, opponent.id].includes(i.user.id),
|
||||
time: this.time,
|
||||
});
|
||||
}
|
||||
|
||||
public HumanGame(
|
||||
interaction: ChatInputCommandInteraction,
|
||||
collector: InteractionCollector<ButtonInteraction<CacheType>>,
|
||||
chance: User,
|
||||
mark: "X" | "O",
|
||||
content: string,
|
||||
pieces: ActionRowBuilder<ButtonBuilder>[],
|
||||
opponent: User
|
||||
) {
|
||||
collector.on("collect", async (i) => {
|
||||
if (chance.id !== i.user.id) {
|
||||
return void (await i.reply({
|
||||
content: "Not your chance mister!",
|
||||
ephemeral: true,
|
||||
}));
|
||||
}
|
||||
const { customId } = i;
|
||||
this.mark(pieces, customId, mark);
|
||||
public HumanGame(
|
||||
interaction: ChatInputCommandInteraction,
|
||||
collector: InteractionCollector<ButtonInteraction<CacheType>>,
|
||||
chance: User,
|
||||
mark: "X" | "O",
|
||||
content: string,
|
||||
pieces: ActionRowBuilder<ButtonBuilder>[],
|
||||
opponent: User,
|
||||
) {
|
||||
collector.on("collect", async (i) => {
|
||||
if (chance.id !== i.user.id) {
|
||||
return void (await i.reply({
|
||||
content: "Not your chance mister!",
|
||||
ephemeral: true,
|
||||
}));
|
||||
}
|
||||
const { customId } = i;
|
||||
this.mark(pieces, customId, mark);
|
||||
|
||||
const possibleWinner = this.computeWin(pieces);
|
||||
if (possibleWinner.winner) {
|
||||
this.disableAllButtons(pieces, possibleWinner.winner, possibleWinner.winPieces);
|
||||
collector.stop(`Finished!`);
|
||||
const possibleWinner = this.computeWin(pieces);
|
||||
if (possibleWinner.winner) {
|
||||
this.disableAllButtons(pieces, possibleWinner.winner, possibleWinner.winPieces);
|
||||
collector.stop(`Finished!`);
|
||||
|
||||
return void (await i.update({
|
||||
content: `${chance} won! GG`,
|
||||
components: pieces,
|
||||
}));
|
||||
}
|
||||
return void (await i.update({
|
||||
content: `${chance} won! GG`,
|
||||
components: pieces,
|
||||
}));
|
||||
}
|
||||
|
||||
chance = chance.id === interaction.user.id ? opponent : interaction.user;
|
||||
mark = mark === "X" ? "O" : "X";
|
||||
chance = chance.id === interaction.user.id ? opponent : interaction.user;
|
||||
mark = mark === "X" ? "O" : "X";
|
||||
|
||||
collector.resetTimer();
|
||||
collector.resetTimer();
|
||||
|
||||
const disabled = this.getDisabled(pieces);
|
||||
const disabled = this.getDisabled(pieces);
|
||||
|
||||
content =
|
||||
disabled === 9
|
||||
? "Game ended in tie, what a shame!"
|
||||
: `Let the game begin!\n${interaction.user} vs ${opponent}\n\n> Current Chance: ${chance} [${mark}]` +
|
||||
`\nTime ends ${new Timestamp(Date.now() + this.time!).getRelativeTime()}`;
|
||||
content =
|
||||
disabled === 9
|
||||
? "Game ended in tie, what a shame!"
|
||||
: `Let the game begin!\n${interaction.user} vs ${opponent}\n\n> Current Chance: ${chance} [${mark}]` +
|
||||
`\nTime ends ${new Timestamp(Date.now() + this.time!).getRelativeTime()}`;
|
||||
|
||||
await i.update({
|
||||
content,
|
||||
components: pieces,
|
||||
});
|
||||
if (disabled === 9) collector.stop("Finished!");
|
||||
});
|
||||
await i.update({
|
||||
content,
|
||||
components: pieces,
|
||||
});
|
||||
if (disabled === 9) collector.stop("Finished!");
|
||||
});
|
||||
|
||||
collector.on("ignore", async (i) => {
|
||||
await i.reply({
|
||||
content: `You ain't playin my man, get rekt`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
collector.on("ignore", async (i) => {
|
||||
await i.reply({
|
||||
content: `You ain't playin my man, get rekt`,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
collector.on("end", async (_, r) => {
|
||||
if (r === "Finished!") return;
|
||||
this.disableAllButtons(pieces);
|
||||
await interaction.editReply({
|
||||
content: `Fine, I ain't playing anymore, won't wait for afk losers`,
|
||||
components: pieces,
|
||||
});
|
||||
});
|
||||
}
|
||||
collector.on("end", async (_, r) => {
|
||||
if (r === "Finished!") return;
|
||||
this.disableAllButtons(pieces);
|
||||
await interaction.editReply({
|
||||
content: `Fine, I ain't playing anymore, won't wait for afk losers`,
|
||||
components: pieces,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getDisabled(pieces: ActionRowBuilder<ButtonBuilder>[]) {
|
||||
let disabled = 0;
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (piece.data.disabled) disabled++;
|
||||
}
|
||||
}
|
||||
return disabled;
|
||||
}
|
||||
public getDisabled(pieces: ActionRowBuilder<ButtonBuilder>[]) {
|
||||
let disabled = 0;
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (piece.data.disabled) disabled++;
|
||||
}
|
||||
}
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public buildButtons(i: number) {
|
||||
return Array<ButtonBuilder>(3)
|
||||
.fill(new ButtonBuilder())
|
||||
.map((_, j) =>
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`${i + 1}-${j + 1}`)
|
||||
.setEmoji(this.#Empty)
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
);
|
||||
}
|
||||
public buildButtons(i: number) {
|
||||
return Array<ButtonBuilder>(3)
|
||||
.fill(new ButtonBuilder())
|
||||
.map((_, j) =>
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`${i + 1}-${j + 1}`)
|
||||
.setEmoji(this.#Empty)
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
}
|
||||
|
||||
public buildRows() {
|
||||
return Array<ActionRowBuilder>(3)
|
||||
.fill(new ActionRowBuilder())
|
||||
.map((_, i) => new ActionRowBuilder<ButtonBuilder>().setComponents(this.buildButtons(i)));
|
||||
}
|
||||
public buildRows() {
|
||||
return Array<ActionRowBuilder>(3)
|
||||
.fill(new ActionRowBuilder())
|
||||
.map((_, i) =>
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(this.buildButtons(i)),
|
||||
);
|
||||
}
|
||||
|
||||
public mark(rows: ActionRowBuilder<ButtonBuilder>[], id: string, mark: "X" | "O") {
|
||||
for (const row of rows) {
|
||||
for (const button of row.components) {
|
||||
if ((button.data as APIButtonComponentWithCustomId).custom_id === id) {
|
||||
button.setEmoji(mark === "O" ? this.#O : this.#X);
|
||||
button.setDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
public mark(rows: ActionRowBuilder<ButtonBuilder>[], id: string, mark: "X" | "O") {
|
||||
for (const row of rows) {
|
||||
for (const button of row.components) {
|
||||
if ((button.data as APIButtonComponentWithCustomId).custom_id === id) {
|
||||
button.setEmoji(mark === "O" ? this.#O : this.#X);
|
||||
button.setDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
public computeWin(pieces: ActionRowBuilder<ButtonBuilder>[]) {
|
||||
const markedX: Combination[] = [];
|
||||
const markedO: Combination[] = [];
|
||||
let winPieces: Combination[] = [];
|
||||
let winner: "X" | "O" | undefined;
|
||||
public computeWin(pieces: ActionRowBuilder<ButtonBuilder>[]) {
|
||||
const markedX: Combination[] = [];
|
||||
const markedO: Combination[] = [];
|
||||
let winPieces: Combination[] = [];
|
||||
let winner: "X" | "O" | undefined;
|
||||
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (!piece.data.disabled) continue;
|
||||
const { emoji } = piece.data;
|
||||
if (!emoji) continue;
|
||||
const emojiString = `<:${emoji.name}:${emoji.id}>`;
|
||||
const id = (piece.data as APIButtonComponentWithCustomId).custom_id as Combination;
|
||||
if (emojiString === this.#X) markedX.push(id);
|
||||
else markedO.push(id);
|
||||
}
|
||||
}
|
||||
if (this.#WinConditions.find((win) => win.every((r) => markedX.includes(r)))) {
|
||||
winPieces = this.#WinConditions.find((win) => win.every((r) => markedX.includes(r)))!;
|
||||
winner = "X";
|
||||
}
|
||||
if (this.#WinConditions.find((win) => win.every((r) => markedO.includes(r)))) {
|
||||
winPieces = this.#WinConditions.find((win) => win.every((r) => markedO.includes(r)))!;
|
||||
winner = "O";
|
||||
}
|
||||
return { winner, winPieces };
|
||||
}
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (!piece.data.disabled) continue;
|
||||
const { emoji } = piece.data;
|
||||
if (!emoji) continue;
|
||||
const emojiString = `<:${emoji.name}:${emoji.id}>`;
|
||||
const id = (piece.data as APIButtonComponentWithCustomId).custom_id as Combination;
|
||||
if (emojiString === this.#X) markedX.push(id);
|
||||
else markedO.push(id);
|
||||
}
|
||||
}
|
||||
if (this.#WinConditions.find((win) => win.every((r) => markedX.includes(r)))) {
|
||||
winPieces = this.#WinConditions.find((win) => win.every((r) => markedX.includes(r)))!;
|
||||
winner = "X";
|
||||
}
|
||||
if (this.#WinConditions.find((win) => win.every((r) => markedO.includes(r)))) {
|
||||
winPieces = this.#WinConditions.find((win) => win.every((r) => markedO.includes(r)))!;
|
||||
winner = "O";
|
||||
}
|
||||
return { winner, winPieces };
|
||||
}
|
||||
|
||||
public disableAllButtons(
|
||||
pieces: ActionRowBuilder<ButtonBuilder>[],
|
||||
winner?: "X" | "O",
|
||||
winPieces?: Combination[]
|
||||
) {
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (winner && winPieces) {
|
||||
const { emoji } = piece.data;
|
||||
if (!emoji) continue;
|
||||
const emojiString = `<:${emoji.name}:${emoji.id}>`;
|
||||
const win = winner === "X" ? this.#X : this.#O;
|
||||
if (
|
||||
win === emojiString &&
|
||||
winPieces.some((w) =>
|
||||
w.includes((piece.data as APIButtonComponentWithCustomId).custom_id)
|
||||
)
|
||||
)
|
||||
piece.setStyle(ButtonStyle.Success);
|
||||
}
|
||||
piece.setDisabled();
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
public disableAllButtons(
|
||||
pieces: ActionRowBuilder<ButtonBuilder>[],
|
||||
winner?: "X" | "O",
|
||||
winPieces?: Combination[],
|
||||
) {
|
||||
for (const row of pieces) {
|
||||
for (const piece of row.components) {
|
||||
if (winner && winPieces) {
|
||||
const { emoji } = piece.data;
|
||||
if (!emoji) continue;
|
||||
const emojiString = `<:${emoji.name}:${emoji.id}>`;
|
||||
const win = winner === "X" ? this.#X : this.#O;
|
||||
if (
|
||||
win === emojiString &&
|
||||
winPieces.some((w) =>
|
||||
w.includes((piece.data as APIButtonComponentWithCustomId).custom_id),
|
||||
)
|
||||
)
|
||||
piece.setStyle(ButtonStyle.Success);
|
||||
}
|
||||
piece.setDisabled();
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
}
|
||||
|
||||
type Side = 1 | 2 | 3;
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
export class Timestamp {
|
||||
/**
|
||||
* Discord Timestamps
|
||||
* @param timestamp The timestamp to convert to a readable string
|
||||
* @requires [UNIX](https://en.wikipedia.org/wiki/Unix_time) timestamp in `milliseconds`
|
||||
*/
|
||||
public constructor(public readonly timestamp: number) {
|
||||
if (this.timestamp < 0) throw new Error("Timestamp must be a positive number");
|
||||
}
|
||||
/**
|
||||
* Discord Timestamps
|
||||
* @param timestamp The timestamp to convert to a readable string
|
||||
* @requires [UNIX](https://en.wikipedia.org/wiki/Unix_time) timestamp in `milliseconds`
|
||||
*/
|
||||
public constructor(public readonly timestamp: number) {
|
||||
if (this.timestamp < 0) throw new Error("Timestamp must be a positive number");
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getRelativeTime();
|
||||
* // => a few seconds ago
|
||||
* ```
|
||||
* @returns {string} The relative time from this timestamp to now
|
||||
*/
|
||||
public getRelativeTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:R>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getRelativeTime();
|
||||
* // => a few seconds ago
|
||||
* ```
|
||||
* @returns {string} The relative time from this timestamp to now
|
||||
*/
|
||||
public getRelativeTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:R>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortDateTime();
|
||||
* // => 5 March 2022 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Date Month Year HH:MM`
|
||||
*/
|
||||
public getShortDateTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:f>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortDateTime();
|
||||
* // => 5 March 2022 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Date Month Year HH:MM`
|
||||
*/
|
||||
public getShortDateTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:f>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongDateTime();
|
||||
* // => Saturday, 5 March 2022 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Day Date Month Year HH:MM`
|
||||
*/
|
||||
public getLongDateTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:F>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongDateTime();
|
||||
* // => Saturday, 5 March 2022 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Day Date Month Year HH:MM`
|
||||
*/
|
||||
public getLongDateTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:F>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortDate();
|
||||
* // => 05/03/2022
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `DD/MM/YYYY`
|
||||
*/
|
||||
public getShortDate(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:d>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortDate();
|
||||
* // => 05/03/2022
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `DD/MM/YYYY`
|
||||
*/
|
||||
public getShortDate(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:d>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongDate();
|
||||
* // => 5 March 2022
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Date Month Year`
|
||||
*/
|
||||
public getLongDate(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:D>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongDate();
|
||||
* // => 5 March 2022
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `Date Month Year`
|
||||
*/
|
||||
public getLongDate(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:D>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortTime();
|
||||
* // => 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `HH:MM`
|
||||
*/
|
||||
public getShortTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:t>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getShortTime();
|
||||
* // => 9:48 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `HH:MM`
|
||||
*/
|
||||
public getShortTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:t>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongTime();
|
||||
* // => 9:48:38 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `HH:MM:SS`
|
||||
*/
|
||||
public getLongTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:T>`;
|
||||
}
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const timestamp = new Timestamp(Date.now());
|
||||
* timestamp.getLongTime();
|
||||
* // => 9:48:38 PM
|
||||
* ```
|
||||
* @returns {string} The date and time in the format of `HH:MM:SS`
|
||||
*/
|
||||
public getLongTime(): string {
|
||||
return `<t:${Math.floor(this.timestamp / 1000)}:T>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { fetch } from "undici";
|
||||
|
||||
export async function upload(code: string, name?: string) {
|
||||
const response = await fetch("https://sourceb.in/api/bins", {
|
||||
body: JSON.stringify({
|
||||
title: "Code",
|
||||
description: "Because I am lazy",
|
||||
files: [{ name, content: code, languageId: 378 }],
|
||||
}),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const data = (await response.json()) as PostData;
|
||||
return `<https://sourceb.in/${data.key}>`;
|
||||
const response = await fetch("https://sourceb.in/api/bins", {
|
||||
body: JSON.stringify({
|
||||
title: "Code",
|
||||
description: "Because I am lazy",
|
||||
files: [{ name, content: code, languageId: 378 }],
|
||||
}),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const data = (await response.json()) as PostData;
|
||||
return `<https://sourceb.in/${data.key}>`;
|
||||
}
|
||||
|
||||
interface PostData {
|
||||
key: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import {
|
||||
AnyCommandPlugin,
|
||||
BaseOptions,
|
||||
commandModule,
|
||||
CommandType,
|
||||
Context,
|
||||
SernSubCommandData,
|
||||
SernSubCommandGroupData,
|
||||
SlashOptions,
|
||||
AnyCommandPlugin,
|
||||
BaseOptions,
|
||||
commandModule,
|
||||
CommandType,
|
||||
Context,
|
||||
SernSubCommandData,
|
||||
SernSubCommandGroupData,
|
||||
SlashOptions,
|
||||
} from "@sern/handler";
|
||||
|
||||
export function slashCommand(data: {
|
||||
name?: string;
|
||||
description: string;
|
||||
plugins?: AnyCommandPlugin[];
|
||||
options?: (SernSubCommandData | SernSubCommandGroupData | BaseOptions)[] | undefined;
|
||||
execute: (ctx: Context, args: ["slash", SlashOptions]) => any;
|
||||
name?: string;
|
||||
description: string;
|
||||
plugins?: AnyCommandPlugin[];
|
||||
options?: (SernSubCommandData | SernSubCommandGroupData | BaseOptions)[] | undefined;
|
||||
execute: (ctx: Context, args: ["slash", SlashOptions]) => any;
|
||||
}) {
|
||||
//Weird fix for explicit undefined fields in an object
|
||||
const resolvedData = { type: CommandType.Slash, ...data } as const;
|
||||
return commandModule(resolvedData);
|
||||
//Weird fix for explicit undefined fields in an object
|
||||
const resolvedData = { type: CommandType.Slash, ...data } as const;
|
||||
return commandModule(resolvedData);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function cutText(text: string) {
|
||||
if (text.length > 100) {
|
||||
return text.slice(0, 97) + "...";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
if (text.length > 100) {
|
||||
return text.slice(0, 97) + "...";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import { ActionRowBuilder, ButtonBuilder, Message, EmbedBuilder } from "discord.js";
|
||||
|
||||
export function Paginate() {
|
||||
const __embeds = [] as EmbedBuilder[];
|
||||
let cur = 0;
|
||||
let traverser: [ButtonBuilder, ButtonBuilder];
|
||||
let message: Message;
|
||||
return {
|
||||
add(...embeds: EmbedBuilder[]) {
|
||||
__embeds.push(...embeds);
|
||||
return this;
|
||||
},
|
||||
setTraverser(tr: [ButtonBuilder, ButtonBuilder]) {
|
||||
traverser = tr;
|
||||
},
|
||||
setMessage(_message: Message) {
|
||||
message = _message;
|
||||
},
|
||||
async next() {
|
||||
cur++;
|
||||
if (cur >= __embeds.length) {
|
||||
cur = 0;
|
||||
}
|
||||
await message.edit(this.components());
|
||||
},
|
||||
async back() {
|
||||
cur--;
|
||||
if (cur <= -__embeds.length) {
|
||||
cur = 0;
|
||||
}
|
||||
await message.edit(this.components());
|
||||
},
|
||||
at(num: number) {
|
||||
return __embeds.at(num);
|
||||
},
|
||||
components() {
|
||||
return {
|
||||
embeds: [__embeds.at(cur)!],
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(traverser[0], traverser[1]),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
const __embeds = [] as EmbedBuilder[];
|
||||
let cur = 0;
|
||||
let traverser: [ButtonBuilder, ButtonBuilder];
|
||||
let message: Message;
|
||||
return {
|
||||
add(...embeds: EmbedBuilder[]) {
|
||||
__embeds.push(...embeds);
|
||||
return this;
|
||||
},
|
||||
setTraverser(tr: [ButtonBuilder, ButtonBuilder]) {
|
||||
traverser = tr;
|
||||
},
|
||||
setMessage(_message: Message) {
|
||||
message = _message;
|
||||
},
|
||||
async next() {
|
||||
cur++;
|
||||
if (cur >= __embeds.length) {
|
||||
cur = 0;
|
||||
}
|
||||
await message.edit(this.components());
|
||||
},
|
||||
async back() {
|
||||
cur--;
|
||||
if (cur <= -__embeds.length) {
|
||||
cur = 0;
|
||||
}
|
||||
await message.edit(this.components());
|
||||
},
|
||||
at(num: number) {
|
||||
return __embeds.at(num);
|
||||
},
|
||||
components() {
|
||||
return {
|
||||
embeds: [__embeds.at(cur)!],
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(traverser[0], traverser[1]),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { ActivityType, Client, ClientPresenceStatus } from "discord.js";
|
||||
|
||||
const statues: [Exclude<ActivityType, ActivityType.Custom>, string, ClientPresenceStatus][] = [
|
||||
[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"],
|
||||
[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"],
|
||||
];
|
||||
|
||||
export function randomStatus(client: Client) {
|
||||
setInterval(() => {
|
||||
const shuffledStatuses = shuffleArray(statues);
|
||||
const [type, name, status] = [...shuffledStatuses].shift()!;
|
||||
client.user!.setPresence({ activities: [{ name, type }], status });
|
||||
}, 60_000);
|
||||
setInterval(() => {
|
||||
const shuffledStatuses = shuffleArray(statues);
|
||||
const [type, name, status] = [...shuffledStatuses].shift()!;
|
||||
client.user!.setPresence({ activities: [{ name, type }], status });
|
||||
}, 60_000);
|
||||
}
|
||||
|
||||
function shuffleArray<T>(array: T[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return [...array];
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return [...array];
|
||||
}
|
||||
|
||||
112
tags.json
112
tags.json
@@ -1,58 +1,58 @@
|
||||
[
|
||||
{
|
||||
"name": "Upgrade DJS",
|
||||
"content": "Please update your discord.js version with compatible version of sern Handler\nIt should be above v14.8.0\n\n> **Tip**: Don't know how to check? Run `npm ls discord.js` in your terminal!",
|
||||
"keywords": ["upgrade your djs"]
|
||||
},
|
||||
{
|
||||
"name": "Outdated Node",
|
||||
"content": "Your nodejs version is outdated, update it to 18.0 or higher",
|
||||
"keywords": ["'node:events'", "upgrade your node", "your node is outdated"]
|
||||
},
|
||||
{
|
||||
"name": "Missing Intents",
|
||||
"content": "If you get an error that says \"Missing Intents\", you probably want to add them. __**Don't include intents that you won't use.**__ You can check what intents you need [here](https://ziad87.net/intents/).",
|
||||
"keywords": ["missing intents"]
|
||||
},
|
||||
{
|
||||
"name": "How2Ask",
|
||||
"content": "Often users face issues like commands not loading, message commands not working, slash commands not registering, etc. when they try sern. Please be specific when asking your question and provide all the proper data like code, error, expected behaviour, version, etc. Below are some more questions you can answer that will help us to guide you without wasting time!\n\n> Did you use [`@sern/cli`](<https://www.npmjs.com/@sern/cli>) to generate your project?\n> Did you use templates as a reference and create project?\n> What version of [`discord.js`](<https://www.npmjs.com/discord.js>) you are on? Is it v13? ||(`npm ls discord.js`)||\n> What version of [`@sern/handler`](<https://www.npmjs.com/@sern/handler>) you are using?\n> Are you using TypeScript or JavaScript?\n> Are you using CommonJS or ESM? ||(Do you have `\"type\": \"module\"` in your package.json?)||",
|
||||
"keywords": ["how 2 ask", "how2ask"]
|
||||
},
|
||||
{
|
||||
"name": "ts-node",
|
||||
"content": "_TL;DR: Do not use [`ts-node`](<https://github.com/TypeStrong/ts-node>), use [`tsc-watch`](<https://www.npmjs.com/package/tsc-watch>) instead._\n\nWe very strongly discourage using [`ts-node`](<https://github.com/TypeStrong/ts-node>) because it was never meant to be used for bots.\n[`ts-node`](<https://github.com/TypeStrong/ts-node>) is designed for [`REPL`](<https://en.wikipedia.org/wiki/Read-eval-print_loop>) purposes. That's short for `Read Eval Print Loop`.\nWhich means to read some code, dump it in an `eval()` statement, print the result, and loop.\n\n**_A discord bot is not that._**\nA Discord bot sets up a permanent websocket connection to the discord server and connects to the rest gateway.\nThere is read yes, but **no eval**, **no print**, and **no loop**.\n\n**So what should you use instead?**\nThe most ideal way is to just use the `watch` flag of `tsc` (`tsc --watch`) and run `node dist/index.js` to run your bot, then cancel that process and restart it when you have changes that require restarting.\nYou would open 2 terminal tabs, 1 in which you run `tsc --watch` and another in which you run the bot.\nThis is in particular the most ideal way, because Discord has a limit to the amount of times you can login with your bot, or register commands, per day.\nConstantly logging in over and over again due to an auto-restarting process will get you close to that limit very quickly and once you exceed it, your development will be halted entirely for the current day.\n\nHowever, this can be quite tedious so a great package to use instead is [`tsc-watch`](<https://www.npmjs.com/package/tsc-watch>).",
|
||||
"keywords": ["don't use ts-node", "use ts-node"]
|
||||
},
|
||||
{
|
||||
"name": "Try",
|
||||
"content": "https://tryitands.ee/",
|
||||
"embed": false,
|
||||
"keywords": ["will this work?"]
|
||||
},
|
||||
{
|
||||
"name": "docs",
|
||||
"content": "Please read the documentation of sern [*here*](<https://sern.dev/docs/intro>)",
|
||||
"keywords": ["sern docs"]
|
||||
},
|
||||
{
|
||||
"name": "ask",
|
||||
"content": "[Don't act like a small kid, the embed is for you](https://dontasktoask.com/)",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "hello",
|
||||
"content": "https://nohello.net/",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"content": "Try reading this quick [blog](<https://sern.dev/blog>) post & watch the videos, if you want to learn the basics of sern.",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "plugins",
|
||||
"content": "Plugins are a great way of adding custom functionality in sern!\nYou can utilize the [community made plugins](<https://github.com/sern-handler/awesome-plugins/>) with [*`@sern/cli`*](<https://github.com/sern-handler/cli>)\n\n> To get the list of plugins, type\n```bash\nsern plugins\n```\nAfter that you can browse the list of plugins and even PR one!\n\n> If you are having issues with JavaScript plugins, kindly refer [this](https://canary.discord.com/channels/889026545715400705/989982308633280522/1028149325395660810)",
|
||||
"keywords": ["what are sern plugins", "how to use plugins"]
|
||||
}
|
||||
{
|
||||
"name": "Upgrade DJS",
|
||||
"content": "Please update your discord.js version with compatible version of sern Handler\nIt should be above v14.8.0\n\n> **Tip**: Don't know how to check? Run `npm ls discord.js` in your terminal!",
|
||||
"keywords": ["upgrade your djs"]
|
||||
},
|
||||
{
|
||||
"name": "Outdated Node",
|
||||
"content": "Your nodejs version is outdated, update it to 18.0 or higher",
|
||||
"keywords": ["'node:events'", "upgrade your node", "your node is outdated"]
|
||||
},
|
||||
{
|
||||
"name": "Missing Intents",
|
||||
"content": "If you get an error that says \"Missing Intents\", you probably want to add them. __**Don't include intents that you won't use.**__ You can check what intents you need [here](https://ziad87.net/intents/).",
|
||||
"keywords": ["missing intents"]
|
||||
},
|
||||
{
|
||||
"name": "How2Ask",
|
||||
"content": "Often users face issues like commands not loading, message commands not working, slash commands not registering, etc. when they try sern. Please be specific when asking your question and provide all the proper data like code, error, expected behaviour, version, etc. Below are some more questions you can answer that will help us to guide you without wasting time!\n\n> Did you use [`@sern/cli`](<https://www.npmjs.com/@sern/cli>) to generate your project?\n> Did you use templates as a reference and create project?\n> What version of [`discord.js`](<https://www.npmjs.com/discord.js>) you are on? Is it v13? ||(`npm ls discord.js`)||\n> What version of [`@sern/handler`](<https://www.npmjs.com/@sern/handler>) you are using?\n> Are you using TypeScript or JavaScript?\n> Are you using CommonJS or ESM? ||(Do you have `\"type\": \"module\"` in your package.json?)||",
|
||||
"keywords": ["how 2 ask", "how2ask"]
|
||||
},
|
||||
{
|
||||
"name": "ts-node",
|
||||
"content": "_TL;DR: Do not use [`ts-node`](<https://github.com/TypeStrong/ts-node>), use [`tsc-watch`](<https://www.npmjs.com/package/tsc-watch>) instead._\n\nWe very strongly discourage using [`ts-node`](<https://github.com/TypeStrong/ts-node>) because it was never meant to be used for bots.\n[`ts-node`](<https://github.com/TypeStrong/ts-node>) is designed for [`REPL`](<https://en.wikipedia.org/wiki/Read-eval-print_loop>) purposes. That's short for `Read Eval Print Loop`.\nWhich means to read some code, dump it in an `eval()` statement, print the result, and loop.\n\n**_A discord bot is not that._**\nA Discord bot sets up a permanent websocket connection to the discord server and connects to the rest gateway.\nThere is read yes, but **no eval**, **no print**, and **no loop**.\n\n**So what should you use instead?**\nThe most ideal way is to just use the `watch` flag of `tsc` (`tsc --watch`) and run `node dist/index.js` to run your bot, then cancel that process and restart it when you have changes that require restarting.\nYou would open 2 terminal tabs, 1 in which you run `tsc --watch` and another in which you run the bot.\nThis is in particular the most ideal way, because Discord has a limit to the amount of times you can login with your bot, or register commands, per day.\nConstantly logging in over and over again due to an auto-restarting process will get you close to that limit very quickly and once you exceed it, your development will be halted entirely for the current day.\n\nHowever, this can be quite tedious so a great package to use instead is [`tsc-watch`](<https://www.npmjs.com/package/tsc-watch>).",
|
||||
"keywords": ["don't use ts-node", "use ts-node"]
|
||||
},
|
||||
{
|
||||
"name": "Try",
|
||||
"content": "https://tryitands.ee/",
|
||||
"embed": false,
|
||||
"keywords": ["will this work?"]
|
||||
},
|
||||
{
|
||||
"name": "docs",
|
||||
"content": "Please read the documentation of sern [*here*](<https://sern.dev/docs/intro>)",
|
||||
"keywords": ["sern docs"]
|
||||
},
|
||||
{
|
||||
"name": "ask",
|
||||
"content": "[Don't act like a small kid, the embed is for you](https://dontasktoask.com/)",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "hello",
|
||||
"content": "https://nohello.net/",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"content": "Try reading this quick [blog](<https://sern.dev/blog>) post & watch the videos, if you want to learn the basics of sern.",
|
||||
"keywords": []
|
||||
},
|
||||
{
|
||||
"name": "plugins",
|
||||
"content": "Plugins are a great way of adding custom functionality in sern!\nYou can utilize the [community made plugins](<https://github.com/sern-handler/awesome-plugins/>) with [*`@sern/cli`*](<https://github.com/sern-handler/cli>)\n\n> To get the list of plugins, type\n```bash\nsern plugins\n```\nAfter that you can browse the list of plugins and even PR one!\n\n> If you are having issues with JavaScript plugins, kindly refer [this](https://canary.discord.com/channels/889026545715400705/989982308633280522/1028149325395660810)",
|
||||
"keywords": ["what are sern plugins", "how to use plugins"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"#plugins": ["src/plugins/index.js"],
|
||||
"#utils": ["src/utils/index.js"],
|
||||
"#constants": ["src/constants.js"]
|
||||
}
|
||||
}
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"#plugins": ["src/plugins/index.js"],
|
||||
"#utils": ["src/utils/index.js"],
|
||||
"#constants": ["src/constants.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
dts: false,
|
||||
entry: ["src/**/*.ts", "!src/**/*.d.ts", "typings/"],
|
||||
format: ["esm"],
|
||||
minify: false,
|
||||
silent: true,
|
||||
skipNodeModulesBundle: true,
|
||||
sourcemap: false,
|
||||
target: "esnext",
|
||||
bundle: false,
|
||||
shims: false,
|
||||
keepNames: true,
|
||||
splitting: false,
|
||||
define: {
|
||||
this: "global",
|
||||
},
|
||||
clean: true,
|
||||
dts: false,
|
||||
entry: ["src/**/*.ts", "!src/**/*.d.ts", "typings/"],
|
||||
format: ["esm"],
|
||||
minify: false,
|
||||
silent: true,
|
||||
skipNodeModulesBundle: true,
|
||||
sourcemap: false,
|
||||
target: "esnext",
|
||||
bundle: false,
|
||||
shims: false,
|
||||
keepNames: true,
|
||||
splitting: false,
|
||||
define: {
|
||||
this: "global",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Message } from "discord.js";
|
||||
|
||||
export interface TagData {
|
||||
name: string;
|
||||
content: string;
|
||||
keywords: string[];
|
||||
embed?: boolean;
|
||||
name: string;
|
||||
content: string;
|
||||
keywords: string[];
|
||||
embed?: boolean;
|
||||
}
|
||||
|
||||
export type TagMessage = Message & { tagTriggerId?: string };
|
||||
|
||||
@@ -1,79 +1,83 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
declare module "trie-search" {
|
||||
type KeyFields = string | string[] | KeyFields[];
|
||||
type KeyFields = string | string[] | KeyFields[];
|
||||
|
||||
type TrieNode<T> = {
|
||||
value?: T[];
|
||||
[key: string]: TrieNode<T> | T[] | undefined;
|
||||
};
|
||||
type TrieNode<T> = {
|
||||
value?: T[];
|
||||
[key: string]: TrieNode<T> | T[] | undefined;
|
||||
};
|
||||
|
||||
type TrieSearchOptions<T> = {
|
||||
ignoreCase?: boolean;
|
||||
maxCacheSize?: number;
|
||||
cache?: boolean;
|
||||
splitOnRegEx?: RegExp;
|
||||
splitOnGetRegEx?: RegExp;
|
||||
min?: number;
|
||||
keepAll?: boolean;
|
||||
keepAllKey?: string;
|
||||
idFieldOrFunction?: string | ((item: T) => string);
|
||||
expandRegexes?: { regex: RegExp; alternate: string }[];
|
||||
insertFullUnsplitKey?: boolean;
|
||||
};
|
||||
type TrieSearchOptions<T> = {
|
||||
ignoreCase?: boolean;
|
||||
maxCacheSize?: number;
|
||||
cache?: boolean;
|
||||
splitOnRegEx?: RegExp;
|
||||
splitOnGetRegEx?: RegExp;
|
||||
min?: number;
|
||||
keepAll?: boolean;
|
||||
keepAllKey?: string;
|
||||
idFieldOrFunction?: string | ((item: T) => string);
|
||||
expandRegexes?: { regex: RegExp; alternate: string }[];
|
||||
insertFullUnsplitKey?: boolean;
|
||||
};
|
||||
|
||||
type Reducer<A, T, I = T> = (
|
||||
accumulator: A | undefined,
|
||||
phrase: string,
|
||||
matches: T[],
|
||||
trieSearch: TrieSearch<T, I>
|
||||
) => A | undefined;
|
||||
type Reducer<A, T, I = T> = (
|
||||
accumulator: A | undefined,
|
||||
phrase: string,
|
||||
matches: T[],
|
||||
trieSearch: TrieSearch<T, I>,
|
||||
) => A | undefined;
|
||||
|
||||
export default class TrieSearch<T, I = T> {
|
||||
constructor(keyFields?: KeyFields, options?: TrieSearchOptions<I>);
|
||||
export default class TrieSearch<T, I = T> {
|
||||
constructor(keyFields?: KeyFields, options?: TrieSearchOptions<I>);
|
||||
|
||||
size: number;
|
||||
size: number;
|
||||
|
||||
root: TrieNode<T>;
|
||||
root: TrieNode<T>;
|
||||
|
||||
add(obj: T, customKeys?: KeyFields | number): void;
|
||||
add(obj: T, customKeys?: KeyFields | number): void;
|
||||
|
||||
expandString(value: string): string[];
|
||||
expandString(value: string): string[];
|
||||
|
||||
addAll(arr: I[], customKeys?: KeyFields | number): void;
|
||||
addAll(arr: I[], customKeys?: KeyFields | number): void;
|
||||
|
||||
reset(): void;
|
||||
reset(): void;
|
||||
|
||||
clearCache(): void;
|
||||
clearCache(): void;
|
||||
|
||||
cleanCache(): void;
|
||||
cleanCache(): void;
|
||||
|
||||
addFromObject(obj: T, valueField?: string): void;
|
||||
addFromObject(obj: T, valueField?: string): void;
|
||||
|
||||
map(key: string, value: T): void;
|
||||
map(key: string, value: T): void;
|
||||
|
||||
keyToArr(key: string): string[];
|
||||
keyToArr(key: string): string[];
|
||||
|
||||
findNode(key: string): TrieNode<T> | undefined;
|
||||
findNode(key: string): TrieNode<T> | undefined;
|
||||
|
||||
_getCacheKey(phrase: string, limit?: number): string;
|
||||
_getCacheKey(phrase: string, limit?: number): string;
|
||||
|
||||
_get(phrase: string, limit?: number): T[];
|
||||
_get(phrase: string, limit?: number): T[];
|
||||
|
||||
get(phrases: string | string[], reducer?: null | undefined, limit?: number): T[];
|
||||
get(phrases: string | string[], reducer?: null | undefined, limit?: number): T[];
|
||||
|
||||
get<A>(phrases: string | string[], reducer?: Reducer<A, T, I>, limit?: number): A | undefined;
|
||||
get<A>(
|
||||
phrases: string | string[],
|
||||
reducer?: Reducer<A, T, I>,
|
||||
limit?: number,
|
||||
): A | undefined;
|
||||
|
||||
search(phrases: string | string[], reducer?: null | undefined): I[];
|
||||
search(phrases: string | string[], reducer?: null | undefined): I[];
|
||||
|
||||
search<A>(phrases: string | string[], reducer: Reducer<A, T, I>): A | undefined;
|
||||
search<A>(phrases: string | string[], reducer: Reducer<A, T, I>): A | undefined;
|
||||
|
||||
getId(item: I): string;
|
||||
getId(item: I): string;
|
||||
|
||||
static UNION_REDUCER: <V>(
|
||||
accumulator: V[] | undefined,
|
||||
phrase: string,
|
||||
matches: V[],
|
||||
trieSearch: TrieSearch<V, V>
|
||||
) => V[];
|
||||
}
|
||||
static UNION_REDUCER: <V>(
|
||||
accumulator: V[] | undefined,
|
||||
phrase: string,
|
||||
matches: V[],
|
||||
trieSearch: TrieSearch<V, V>,
|
||||
) => V[];
|
||||
}
|
||||
}
|
||||
|
||||
1108
typings/docs.ts
1108
typings/docs.ts
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
export * from './docs.js'
|
||||
export * from './plugin.js'
|
||||
export * from './Tags.js'
|
||||
export * from "./docs.js";
|
||||
export * from "./plugin.js";
|
||||
export * from "./Tags.js";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export interface Plugin {
|
||||
description: string;
|
||||
hash: string;
|
||||
name: string;
|
||||
author: string[];
|
||||
link: string;
|
||||
example: string;
|
||||
version: string;
|
||||
description: string;
|
||||
hash: string;
|
||||
name: string;
|
||||
author: string[];
|
||||
link: string;
|
||||
example: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user