chore: init

This commit is contained in:
HerozDotExe 2025-06-18 19:23:32 +02:00
commit a07d554b41
10 changed files with 310 additions and 0 deletions

3
.env.example Normal file
View file

@ -0,0 +1,3 @@
token=
clientId=
guildId=

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# CharCord
Talk to AIs from character.ai directly into a discord voice channel.

27
commands.ts Normal file
View file

@ -0,0 +1,27 @@
import * as path from "jsr:@std/path";
export async function gatherCommandsPaths(): Promise<string[]> {
const commandsPath: string[] = [];
const foldersPath = path.join(import.meta.dirname!, "commands");
const commandsRoot = Deno.readDir(foldersPath);
for await (const entry of commandsRoot) {
if (entry.isDirectory) {
const commandsFolderPath = path.join(foldersPath, entry.name);
const commandsFiles = (
await Array.fromAsync(Deno.readDir(commandsFolderPath))
).filter((file) => file.name.endsWith(".js"));
for (const file of commandsFiles) {
const commandPath = path.join(commandsFolderPath, file.name);
commandsPath.push(commandPath);
}
} else if (entry.name !== "template.ts") {
const commandPath = path.join(foldersPath, entry.name);
commandsPath.push(commandPath);
}
}
return commandsPath;
}

10
commands/template.ts Normal file
View file

@ -0,0 +1,10 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from "npm:discord.js";
export default {
data: new SlashCommandBuilder()
.setName("template")
.setDescription("Template command"),
async execute(interaction: ChatInputCommandInteraction<CacheType>) {
await interaction.reply("Pong!");
},
};

6
deno.json Normal file
View file

@ -0,0 +1,6 @@
{
"tasks": {
"dev": "deno run --watch --env-file --allow-all main.ts",
"deployCommands": "deno run --env-file --allow-all scripts/deployCommands.ts"
}
}

146
deno.lock generated Normal file
View file

@ -0,0 +1,146 @@
{
"version": "4",
"specifiers": {
"jsr:@std/path@*": "1.1.0",
"npm:@types/node@*": "22.12.0",
"npm:discord.js@*": "14.20.0"
},
"jsr": {
"@std/path@1.1.0": {
"integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886"
}
},
"npm": {
"@discordjs/builders@1.11.2": {
"integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==",
"dependencies": [
"@discordjs/formatters",
"@discordjs/util",
"@sapphire/shapeshift",
"discord-api-types",
"fast-deep-equal",
"ts-mixer",
"tslib"
]
},
"@discordjs/collection@1.5.3": {
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="
},
"@discordjs/collection@2.1.1": {
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
},
"@discordjs/formatters@0.6.1": {
"integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==",
"dependencies": [
"discord-api-types"
]
},
"@discordjs/rest@2.5.1": {
"integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==",
"dependencies": [
"@discordjs/collection@2.1.1",
"@discordjs/util",
"@sapphire/async-queue",
"@sapphire/snowflake",
"@vladfrangu/async_event_emitter",
"discord-api-types",
"magic-bytes.js",
"tslib",
"undici"
]
},
"@discordjs/util@1.1.1": {
"integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="
},
"@discordjs/ws@1.2.3": {
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
"dependencies": [
"@discordjs/collection@2.1.1",
"@discordjs/rest",
"@discordjs/util",
"@sapphire/async-queue",
"@types/ws",
"@vladfrangu/async_event_emitter",
"discord-api-types",
"tslib",
"ws"
]
},
"@sapphire/async-queue@1.5.5": {
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="
},
"@sapphire/shapeshift@4.0.0": {
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
"dependencies": [
"fast-deep-equal",
"lodash"
]
},
"@sapphire/snowflake@3.5.3": {
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
},
"@types/node@22.12.0": {
"integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
"dependencies": [
"undici-types"
]
},
"@types/ws@8.18.1": {
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dependencies": [
"@types/node"
]
},
"@vladfrangu/async_event_emitter@2.4.6": {
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="
},
"discord-api-types@0.38.12": {
"integrity": "sha512-vqkRM50N5Zc6OVckAqtSslbUEoXmpN4bd9xq2jkoK9fgO3KNRIOyMMQ7ipqjwjKuAgzWvU6G8bRIcYWaUe1sCA=="
},
"discord.js@14.20.0": {
"integrity": "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg==",
"dependencies": [
"@discordjs/builders",
"@discordjs/collection@1.5.3",
"@discordjs/formatters",
"@discordjs/rest",
"@discordjs/util",
"@discordjs/ws",
"@sapphire/snowflake",
"discord-api-types",
"fast-deep-equal",
"lodash.snakecase",
"magic-bytes.js",
"tslib",
"undici"
]
},
"fast-deep-equal@3.1.3": {
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"lodash.snakecase@4.1.1": {
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"lodash@4.17.21": {
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"magic-bytes.js@1.12.1": {
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="
},
"ts-mixer@6.0.4": {
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"undici-types@6.20.0": {
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
},
"undici@6.21.3": {
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="
},
"ws@8.18.2": {
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="
}
}
}

64
main.ts Normal file
View file

@ -0,0 +1,64 @@
import {
Client as _client,
ClientOptions,
Collection,
Events,
GatewayIntentBits,
MessageFlags,
} from "npm:discord.js";
const token = Deno.env.get("token");
import { Command } from "./types.ts";
import { gatherCommandsPaths } from "./commands.ts";
class Client extends _client {
commands: Collection<string, Command>;
constructor(options: ClientOptions) {
super(options);
this.commands = new Collection();
}
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
async function register(commandPath: string) {
const { default: command }: { default: Command } = await import(commandPath);
if ("data" in command && "execute" in command) {
client.commands.set(command.data.name, command);
console.log(`Registered ${command.data.name}.`);
} else {
console.log(
`[WARNING] The command at ${commandPath} is missing a required "data" or "execute" property.`
);
}
}
(await gatherCommandsPaths()).forEach(register)
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
} else {
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
}
}
});
client.login(token);

42
scripts/deployCommands.ts Normal file
View file

@ -0,0 +1,42 @@
import { REST, RESTPostAPIChatInputApplicationCommandsJSONBody, Routes } from "npm:discord.js";
const { clientId, guildId, token } = Deno.env.toObject();
import { Command } from "../types.ts";
import { gatherCommandsPaths } from "../commands.ts";
const commands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = [];
async function register(commandPath: string) {
const { default: command }: { default: Command } = await import(commandPath);
if ("data" in command && "execute" in command) {
commands.push(command.data.toJSON());
console.log(`Deploying ${command.data.name}...`);
} else {
console.log(
`[WARNING] The command at ${commandPath} is missing a required "data" or "execute" property.`
);
}
}
for (const commandPath of await gatherCommandsPaths()) {
await register(commandPath)
}
const rest = new REST().setToken(token);
(async () => {
try {
console.log(
`Started refreshing ${commands.length} application (/) commands.`
);
await rest.put(
Routes.applicationGuildCommands(clientId, guildId),
{ body: commands }
);
console.log(
`Successfully reloaded ${commands.length} application (/) commands.`
);
} catch (error) {
console.error(error);
}
})();

8
types.ts Normal file
View file

@ -0,0 +1,8 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from "npm:discord.js";
export type Command = {
data: SlashCommandBuilder;
execute: (
interaction: ChatInputCommandInteraction<CacheType>
) => Promise<void>;
};