First iteration!

This commit is contained in:
Atridad Lahiji
2023-05-28 20:20:00 -06:00
commit 34100aac98
23 changed files with 1203 additions and 0 deletions

2
src/.env.example Normal file
View File

@ -0,0 +1,2 @@
# Tokens
DISCORD_TOKEN=

46
src/commands/ping.ts Normal file
View File

@ -0,0 +1,46 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Command } from '@sapphire/framework';
import { Message } from 'discord.js';
@ApplyOptions<Command.Options>({
description: 'Pong!'
})
export class UserCommand extends Command {
// Register Chat Input and Context Menu command
public override registerApplicationCommands(registry: Command.Registry) {
// Register Chat Input command
registry.registerChatInputCommand({
name: this.name,
description: this.description
});
}
// Message command
public async messageRun(message: Message) {
return this.sendPing(message);
}
// Chat Input (slash) command
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
return this.sendPing(interaction);
}
private async sendPing(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction) {
const pingMessage =
interactionOrMessage instanceof Message
? await interactionOrMessage.channel.send({ content: 'Ping?' })
: await interactionOrMessage.reply({ content: 'Ping?', fetchReply: true });
const content = `Pong! Bot Latency ${Math.round(this.container.client.ws.ping)}ms. API Latency ${
pingMessage.createdTimestamp - interactionOrMessage.createdTimestamp
}ms.`;
if (interactionOrMessage instanceof Message) {
return pingMessage.edit({ content });
}
return interactionOrMessage.editReply({
content: content
});
}
}

41
src/commands/wryna.ts Normal file
View File

@ -0,0 +1,41 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Args, Command } from '@sapphire/framework';
import { Message } from 'discord.js';
@ApplyOptions<Command.Options>({
description: 'This command was your nickname in highschool!',
options: ['nickname']
})
export class UserCommand extends Command {
// Register Chat Input and Context Menu command
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
.addStringOption((option) => option.setName('nickname').setDescription('Your nickname in highschool.').setRequired(true))
);
}
// Message command
public async messageRun(message: Message, args: Args) {
return this.sendPing(message, args.getOption('nickname') || 'NOTHING');
}
// Chat Input (slash) command
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
return this.sendPing(interaction, interaction.options.getString('nickname') || 'NOTHING');
}
private async sendPing(
interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction,
nickname: string
) {
interactionOrMessage instanceof Message
? await interactionOrMessage.channel.send({ content: `${nickname} was ${interactionOrMessage.author.tag}'s nickname in highschool!` })
: await interactionOrMessage.reply({
content: `${nickname} was ${interactionOrMessage.user.tag}'s nickname in highschool!`,
fetchReply: true
});
}
}

36
src/index.ts Normal file
View File

@ -0,0 +1,36 @@
import './lib/setup';
import { LogLevel, SapphireClient } from '@sapphire/framework';
import { ActivityType, GatewayIntentBits } from 'discord.js';
const client = new SapphireClient({
defaultPrefix: '!',
presence: {
status: 'online',
activities: [
{
name: "I'm just here for the vibes my dudes...",
type: ActivityType.Playing
}
]
},
caseInsensitiveCommands: true,
logger: {
level: LogLevel.Debug
},
intents: [GatewayIntentBits.DirectMessages, GatewayIntentBits.GuildMessages, GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent],
loadMessageCommandListeners: true
});
const main = async () => {
try {
client.logger.info('Logging in');
await client.login();
client.logger.info('logged in');
} catch (error) {
client.logger.fatal(error);
client.destroy();
process.exit(1);
}
};
main();

4
src/lib/constants.ts Normal file
View File

@ -0,0 +1,4 @@
import { join } from 'path';
export const rootDir = join(__dirname, '..', '..');
export const srcDir = join(rootDir, 'src');

18
src/lib/setup.ts Normal file
View File

@ -0,0 +1,18 @@
// Unless explicitly defined, set NODE_ENV as development:
process.env.NODE_ENV ??= 'development';
import { ApplicationCommandRegistries, RegisterBehavior } from '@sapphire/framework';
import '@sapphire/plugin-logger/register';
import { setup } from '@skyra/env-utilities';
import * as colorette from 'colorette';
import { join } from 'node:path';
import { srcDir } from './constants';
// Set default behavior to bulk overwrite
ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite);
// Read env var
setup({ path: join(srcDir, '.env') });
// Enable colorette
colorette.createColors({ useColor: true });

47
src/lib/utils.ts Normal file
View File

@ -0,0 +1,47 @@
import {
container,
type ChatInputCommandSuccessPayload,
type Command,
type ContextMenuCommandSuccessPayload,
type MessageCommandSuccessPayload
} from '@sapphire/framework';
import { cyan } from 'colorette';
import type { APIUser, Guild, User } from 'discord.js';
export function logSuccessCommand(payload: ContextMenuCommandSuccessPayload | ChatInputCommandSuccessPayload | MessageCommandSuccessPayload): void {
let successLoggerData: ReturnType<typeof getSuccessLoggerData>;
if ('interaction' in payload) {
successLoggerData = getSuccessLoggerData(payload.interaction.guild, payload.interaction.user, payload.command);
} else {
successLoggerData = getSuccessLoggerData(payload.message.guild, payload.message.author, payload.command);
}
container.logger.debug(`${successLoggerData.shard} - ${successLoggerData.commandName} ${successLoggerData.author} ${successLoggerData.sentAt}`);
}
export function getSuccessLoggerData(guild: Guild | null, user: User, command: Command) {
const shard = getShardInfo(guild?.shardId ?? 0);
const commandName = getCommandInfo(command);
const author = getAuthorInfo(user);
const sentAt = getGuildInfo(guild);
return { shard, commandName, author, sentAt };
}
function getShardInfo(id: number) {
return `[${cyan(id.toString())}]`;
}
function getCommandInfo(command: Command) {
return cyan(command.name);
}
function getAuthorInfo(author: User | APIUser) {
return `${author.username}[${cyan(author.id)}]`;
}
function getGuildInfo(guild: Guild | null) {
if (guild === null) return 'Direct Messages';
return `${guild.name}[${cyan(guild.id)}]`;
}

View File

@ -0,0 +1,23 @@
import type { ChatInputCommandDeniedPayload, Events } from '@sapphire/framework';
import { Listener, UserError } from '@sapphire/framework';
export class UserEvent extends Listener<typeof Events.ChatInputCommandDenied> {
public async run({ context, message: content }: UserError, { interaction }: ChatInputCommandDeniedPayload) {
// `context: { silent: true }` should make UserError silent:
// Use cases for this are for example permissions error when running the `eval` command.
if (Reflect.get(Object(context), 'silent')) return;
if (interaction.deferred || interaction.replied) {
return interaction.editReply({
content,
allowedMentions: { users: [interaction.user.id], roles: [] }
});
}
return interaction.reply({
content,
allowedMentions: { users: [interaction.user.id], roles: [] },
ephemeral: true
});
}
}

View File

@ -0,0 +1,14 @@
import { Listener, LogLevel, type ChatInputCommandSuccessPayload } from '@sapphire/framework';
import type { Logger } from '@sapphire/plugin-logger';
import { logSuccessCommand } from '../../../lib/utils';
export class UserListener extends Listener {
public run(payload: ChatInputCommandSuccessPayload) {
logSuccessCommand(payload);
}
public onLoad() {
this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug;
return super.onLoad();
}
}

View File

@ -0,0 +1,23 @@
import type { ContextMenuCommandDeniedPayload, Events } from '@sapphire/framework';
import { Listener, UserError } from '@sapphire/framework';
export class UserEvent extends Listener<typeof Events.ContextMenuCommandDenied> {
public async run({ context, message: content }: UserError, { interaction }: ContextMenuCommandDeniedPayload) {
// `context: { silent: true }` should make UserError silent:
// Use cases for this are for example permissions error when running the `eval` command.
if (Reflect.get(Object(context), 'silent')) return;
if (interaction.deferred || interaction.replied) {
return interaction.editReply({
content,
allowedMentions: { users: [interaction.user.id], roles: [] }
});
}
return interaction.reply({
content,
allowedMentions: { users: [interaction.user.id], roles: [] },
ephemeral: true
});
}
}

View File

@ -0,0 +1,14 @@
import { Listener, LogLevel, type ContextMenuCommandSuccessPayload } from '@sapphire/framework';
import type { Logger } from '@sapphire/plugin-logger';
import { logSuccessCommand } from '../../../lib/utils';
export class UserListener extends Listener {
public run(payload: ContextMenuCommandSuccessPayload) {
logSuccessCommand(payload);
}
public onLoad() {
this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug;
return super.onLoad();
}
}

View File

@ -0,0 +1,12 @@
import type { Events, MessageCommandDeniedPayload } from '@sapphire/framework';
import { Listener, type UserError } from '@sapphire/framework';
export class UserEvent extends Listener<typeof Events.MessageCommandDenied> {
public async run({ context, message: content }: UserError, { message }: MessageCommandDeniedPayload) {
// `context: { silent: true }` should make UserError silent:
// Use cases for this are for example permissions error when running the `eval` command.
if (Reflect.get(Object(context), 'silent')) return;
return message.reply({ content, allowedMentions: { users: [message.author.id], roles: [] } });
}
}

View File

@ -0,0 +1,15 @@
import type { MessageCommandSuccessPayload } from '@sapphire/framework';
import { Listener, LogLevel } from '@sapphire/framework';
import type { Logger } from '@sapphire/plugin-logger';
import { logSuccessCommand } from '../../../lib/utils';
export class UserEvent extends Listener {
public run(payload: MessageCommandSuccessPayload) {
logSuccessCommand(payload);
}
public onLoad() {
this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug;
return super.onLoad();
}
}

View File

@ -0,0 +1,10 @@
import type { Events } from '@sapphire/framework';
import { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js';
export class UserEvent extends Listener<typeof Events.MentionPrefixOnly> {
public async run(message: Message) {
const prefix = this.container.client.options.defaultPrefix;
return message.channel.send(prefix ? `My prefix in this guild is: \`${prefix}\`` : 'Cannot find any Prefix for Message Commands.');
}
}

50
src/listeners/ready.ts Normal file
View File

@ -0,0 +1,50 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Listener, Store } from '@sapphire/framework';
import { blue, gray, green, magenta, magentaBright, white, yellow } from 'colorette';
const dev = process.env.NODE_ENV !== 'production';
@ApplyOptions<Listener.Options>({ once: true })
export class UserEvent extends Listener {
private readonly style = dev ? yellow : blue;
public run() {
this.printBanner();
this.printStoreDebugInformation();
}
private printBanner() {
const success = green('+');
const llc = dev ? magentaBright : white;
const blc = dev ? magenta : blue;
const line01 = llc('');
const line02 = llc('');
const line03 = llc('');
// Offset Pad
const pad = ' '.repeat(7);
console.log(
String.raw`
${line01} ${pad}${blc('1.0.0')}
${line02} ${pad}[${success}] Gateway
${line03}${dev ? ` ${pad}${blc('<')}${llc('/')}${blc('>')} ${llc('DEVELOPMENT MODE')}` : ''}
`.trim()
);
}
private printStoreDebugInformation() {
const { client, logger } = this.container;
const stores = [...client.stores.values()];
const last = stores.pop()!;
for (const store of stores) logger.info(this.styleStore(store, false));
logger.info(this.styleStore(last, true));
}
private styleStore(store: Store<any>, last: boolean) {
return gray(`${last ? '└─' : '├─'} Loaded ${this.style(store.size.toString().padEnd(3, ' '))} ${store.name}.`);
}
}