mirror of
https://github.com/Xevion/leebot.git
synced 2025-12-06 03:15:27 -06:00
Initial commit
Created using ``@sapphire/cli`
This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Tokens
|
||||||
|
DISCORD_TOKEN=
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
DEFAULT_PREFIX=
|
||||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Ignore a blackhole and the folder for development
|
||||||
|
node_modules/
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# Yarn files
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.yarn/build-state.yml
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Ignore the config file (contains sensitive information such as tokens)
|
||||||
|
config.ts
|
||||||
|
|
||||||
|
# Ignore heapsnapshot and log files
|
||||||
|
*.heapsnapshot
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Ignore npm lockfiles file
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
14
.sapphirerc.json
Normal file
14
.sapphirerc.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"projectLanguage": "ts",
|
||||||
|
"locations": {
|
||||||
|
"base": "src",
|
||||||
|
"arguments": "arguments",
|
||||||
|
"commands": "commands",
|
||||||
|
"listeners": "listeners",
|
||||||
|
"preconditions": "preconditions"
|
||||||
|
},
|
||||||
|
"customFileTemplates": {
|
||||||
|
"enabled": false,
|
||||||
|
"location": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
90
Dockerfile
Normal file
90
Dockerfile
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# ================ #
|
||||||
|
# Base Stage #
|
||||||
|
# ================ #
|
||||||
|
|
||||||
|
FROM node:16-buster-slim as base
|
||||||
|
|
||||||
|
WORKDIR /opt/app
|
||||||
|
|
||||||
|
ENV HUSKY=0
|
||||||
|
ENV CI=true
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get upgrade -y --no-install-recommends && \
|
||||||
|
apt-get install -y --no-install-recommends build-essential python3 libfontconfig1 dumb-init && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ------------------------------------ #
|
||||||
|
# Conditional steps for end-users #
|
||||||
|
# ------------------------------------ #
|
||||||
|
|
||||||
|
# Enable one of the following depending on whether you use yarn or npm, then remove the other one
|
||||||
|
# COPY --chown=node:node yarn.lock .
|
||||||
|
# COPY --chown=node:node package-lock.json .
|
||||||
|
|
||||||
|
# If you use Yarn v3 then enable the following lines:
|
||||||
|
# COPY --chown=node:node .yarnrc.yml .
|
||||||
|
# COPY --chown=node:node .yarn/ .yarn/
|
||||||
|
|
||||||
|
# If you have an additional "tsconfig.base.json" file then enable the following line:
|
||||||
|
# COPY --chown=node:node tsconfig.base.json tsconfig.base.json
|
||||||
|
|
||||||
|
# If you require additional NodeJS flags then specify them here
|
||||||
|
ENV NODE_OPTIONS="--enable-source-maps"
|
||||||
|
|
||||||
|
# ---------------------------------------- #
|
||||||
|
# End Conditional steps for end-users #
|
||||||
|
# ---------------------------------------- #
|
||||||
|
|
||||||
|
COPY --chown=node:node package.json .
|
||||||
|
COPY --chown=node:node tsconfig.json .
|
||||||
|
|
||||||
|
RUN sed -i 's/"prepare": "husky install\( .github\/husky\)\?"/"prepare": ""/' ./package.json
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
|
||||||
|
# =================== #
|
||||||
|
# Development Stage #
|
||||||
|
# =================== #
|
||||||
|
|
||||||
|
# Development, used for development only (defaults to watch command)
|
||||||
|
FROM base as development
|
||||||
|
|
||||||
|
ENV NODE_ENV="development"
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
CMD [ "npm", "run", "docker:watch"]
|
||||||
|
|
||||||
|
# ================ #
|
||||||
|
# Builder Stage #
|
||||||
|
# ================ #
|
||||||
|
|
||||||
|
# Build stage for production
|
||||||
|
FROM base as build
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . /opt/app
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ==================== #
|
||||||
|
# Production Stage #
|
||||||
|
# ==================== #
|
||||||
|
|
||||||
|
# Production image used to run the bot in production, only contains node_modules & dist contents.
|
||||||
|
FROM base as production
|
||||||
|
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
|
COPY --from=build /opt/app/dist /opt/app/dist
|
||||||
|
COPY --from=build /opt/app/node_modules /opt/app/node_modules
|
||||||
|
COPY --from=build /opt/app/package.json /opt/app/package.json
|
||||||
|
|
||||||
|
RUN chown node:node /opt/app/
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
CMD [ "npm", "run", "start"]
|
||||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Docker Sapphire Bot example
|
||||||
|
|
||||||
|
This is a basic setup of a Discord bot using the [sapphire framework][sapphire] written in TypeScript containerized with Docker
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
### Prerequisite
|
||||||
|
|
||||||
|
1. Copy the `.env.example` and rename it to `.env`, make sure to fill the Token.
|
||||||
|
2. Open the Dockerfile and in the block marked with `Conditional steps for end-users` make sure you enable the lines that apply to your project.
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
Run `docker-compose up`. This will start the bot in watch mode and automatically run it after each save.
|
||||||
|
It will build the `Dockerfile` up until the `development` stage.
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
Just like in the development step, you have to fill in the `.env` file and then run the following command to create a production image;
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build . -t sapphire-leebot
|
||||||
|
```
|
||||||
|
|
||||||
|
To test if your image works, you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --env-file .env sapphire-leebot
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Dedicated to the public domain via the [Unlicense], courtesy of the Sapphire Community and its contributors.
|
||||||
|
|
||||||
|
[sapphire]: https://github.com/sapphiredev/framework
|
||||||
|
[unlicense]: https://github.com/sapphiredev/examples/blob/main/LICENSE.md
|
||||||
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
sapphire-leebot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: development
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- ./:/opt/app
|
||||||
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "leebot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"author": "@sapphire",
|
||||||
|
"license": "UNLICENSE",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/collection": "^0.4.0",
|
||||||
|
"@sapphire/decorators": "^4.0.2",
|
||||||
|
"@sapphire/discord-utilities": "^2.6.0",
|
||||||
|
"@sapphire/discord.js-utilities": "^4.4.0",
|
||||||
|
"@sapphire/fetch": "^2.0.4",
|
||||||
|
"@sapphire/framework": "^2.3.0",
|
||||||
|
"@sapphire/plugin-api": "^3.1.3",
|
||||||
|
"@sapphire/plugin-editable-commands": "*",
|
||||||
|
"@sapphire/plugin-logger": "^2.1.2",
|
||||||
|
"@sapphire/plugin-subcommands": "^2.1.3",
|
||||||
|
"@sapphire/time-utilities": "^1.5.2",
|
||||||
|
"@sapphire/type": "^2.1.2",
|
||||||
|
"@sapphire/utilities": "^3.2.1",
|
||||||
|
"discord.js": "^13.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sapphire/prettier-config": "^1.2.9",
|
||||||
|
"@sapphire/ts-config": "^3.1.8",
|
||||||
|
"@types/node": "^17.0.10",
|
||||||
|
"@types/ws": "^8.2.2",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"tsc-watch": "^4.6.0",
|
||||||
|
"typescript": "^4.5.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"dev": "run-s build start",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
|
"predocker:watch": "npm install",
|
||||||
|
"docker:watch": "tsc-watch --onSuccess \"node ./dist/index.js\""
|
||||||
|
},
|
||||||
|
"prettier": "@sapphire/prettier-config"
|
||||||
|
}
|
||||||
21
src/commands/General/ping.ts
Normal file
21
src/commands/General/ping.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Command, CommandOptions, PieceContext } from '@sapphire/framework';
|
||||||
|
import type { Message } from 'discord.js';
|
||||||
|
|
||||||
|
export class UserCommand extends Command {
|
||||||
|
public constructor(context: PieceContext, options: CommandOptions) {
|
||||||
|
super(context, {
|
||||||
|
...options,
|
||||||
|
description: 'ping pong'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async messageRun(message: Message) {
|
||||||
|
const msg = await message.channel.send('Ping?');
|
||||||
|
|
||||||
|
return msg.edit(
|
||||||
|
`Pong from Docker! Bot Latency ${Math.round(this.container.client.ws.ping)}ms. API Latency ${
|
||||||
|
msg.createdTimestamp - message.createdTimestamp
|
||||||
|
}ms.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/index.ts
Normal file
37
src/index.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import './lib/setup';
|
||||||
|
import { LogLevel, SapphireClient } from '@sapphire/framework';
|
||||||
|
|
||||||
|
const client = new SapphireClient({
|
||||||
|
defaultPrefix: process.env.DEFAULT_PREFIX,
|
||||||
|
regexPrefix: /^(hey +)?bot[,! ]/i,
|
||||||
|
caseInsensitiveCommands: true,
|
||||||
|
logger: {
|
||||||
|
level: LogLevel.Debug
|
||||||
|
},
|
||||||
|
shards: 'auto',
|
||||||
|
intents: [
|
||||||
|
'GUILDS',
|
||||||
|
'GUILD_MEMBERS',
|
||||||
|
'GUILD_BANS',
|
||||||
|
'GUILD_EMOJIS_AND_STICKERS',
|
||||||
|
'GUILD_VOICE_STATES',
|
||||||
|
'GUILD_MESSAGES',
|
||||||
|
'GUILD_MESSAGE_REACTIONS',
|
||||||
|
'DIRECT_MESSAGES',
|
||||||
|
'DIRECT_MESSAGE_REACTIONS'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
11
src/lib/setup.ts
Normal file
11
src/lib/setup.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import '@sapphire/plugin-logger/register';
|
||||||
|
import '@sapphire/plugin-api/register';
|
||||||
|
import '@sapphire/plugin-editable-commands/register';
|
||||||
|
import * as colorette from 'colorette';
|
||||||
|
import { inspect } from 'util';
|
||||||
|
|
||||||
|
// Set default inspection depth
|
||||||
|
inspect.defaultOptions.depth = 1;
|
||||||
|
|
||||||
|
// Enable colorette
|
||||||
|
colorette.createColors({ useColor: true });
|
||||||
47
src/listeners/commands/commandSuccessLogger.ts
Normal file
47
src/listeners/commands/commandSuccessLogger.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import type { CommandSuccessPayload, ListenerOptions, PieceContext } from '@sapphire/framework';
|
||||||
|
import { Command, Events, Listener, LogLevel } from '@sapphire/framework';
|
||||||
|
import type { Logger } from '@sapphire/plugin-logger';
|
||||||
|
import { cyan } from 'colorette';
|
||||||
|
import type { Guild, User } from 'discord.js';
|
||||||
|
|
||||||
|
export class UserEvent extends Listener<typeof Events.CommandSuccess> {
|
||||||
|
public constructor(context: PieceContext, options?: ListenerOptions) {
|
||||||
|
super(context, {
|
||||||
|
...options,
|
||||||
|
event: Events.CommandSuccess
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public run({ message, command }: CommandSuccessPayload) {
|
||||||
|
const shard = this.shard(message.guild?.shardId ?? 0);
|
||||||
|
const commandName = this.command(command);
|
||||||
|
const author = this.author(message.author);
|
||||||
|
const sentAt = message.guild ? this.guild(message.guild) : this.direct();
|
||||||
|
this.container.logger.debug(`${shard} - ${commandName} ${author} ${sentAt}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onLoad() {
|
||||||
|
this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug;
|
||||||
|
return super.onLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private shard(id: number) {
|
||||||
|
return `[${cyan(id.toString())}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private command(command: Command) {
|
||||||
|
return cyan(command.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private author(author: User) {
|
||||||
|
return `${author.username}[${cyan(author.id)}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private direct() {
|
||||||
|
return cyan('Direct Messages');
|
||||||
|
}
|
||||||
|
|
||||||
|
private guild(guild: Guild) {
|
||||||
|
return `${guild.name}[${cyan(guild.id)}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/listeners/mentionPrefixOnly.ts
Normal file
9
src/listeners/mentionPrefixOnly.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Listener } from '@sapphire/framework';
|
||||||
|
import type { Message } from 'discord.js';
|
||||||
|
|
||||||
|
export class UserEvent extends Listener<'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}\`` : 'You do not need a prefix in DMs.');
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/listeners/messages/messageUpdate.ts
Normal file
21
src/listeners/messages/messageUpdate.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Listener } from '@sapphire/framework';
|
||||||
|
import type { Message } from 'discord.js';
|
||||||
|
|
||||||
|
export class UserEvent extends Listener {
|
||||||
|
public run(old: Message, message: Message) {
|
||||||
|
// If the contents of both messages are the same, return:
|
||||||
|
if (old.content === message.content) return;
|
||||||
|
|
||||||
|
// If the message was sent by a webhook, return:
|
||||||
|
if (message.webhookId !== null) return;
|
||||||
|
|
||||||
|
// If the message was sent by the system, return:
|
||||||
|
if (message.system) return;
|
||||||
|
|
||||||
|
// If the message was sent by a bot, return:
|
||||||
|
if (message.author.bot) return;
|
||||||
|
|
||||||
|
// Run the message parser.
|
||||||
|
this.container.client.emit('preMessageParsed', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/listeners/ready.ts
Normal file
56
src/listeners/ready.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import type { ListenerOptions, PieceContext } from '@sapphire/framework';
|
||||||
|
import { Listener, Store } from '@sapphire/framework';
|
||||||
|
import { blue, gray, green, magenta, magentaBright, white, yellow } from 'colorette';
|
||||||
|
|
||||||
|
const dev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
export class UserEvent extends Listener {
|
||||||
|
private readonly style = dev ? yellow : blue;
|
||||||
|
|
||||||
|
public constructor(context: PieceContext, options?: ListenerOptions) {
|
||||||
|
super(context, {
|
||||||
|
...options,
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@sapphire/ts-config",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user