Initialize Sapphire project
This commit is contained in:
parent
a9f52544c7
commit
639bbc1126
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -0,0 +1,5 @@
|
|||
# Tokens
|
||||
DISCORD_TOKEN=
|
||||
|
||||
# Configuration
|
||||
DEFAULT_PREFIX=
|
|
@ -0,0 +1,43 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,yarn
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,yarn
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### yarn ###
|
||||
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
|
||||
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# if you are NOT using Zero-installs, then:
|
||||
# comment the following lines
|
||||
!.yarn/cache
|
||||
|
||||
# and uncomment the following lines
|
||||
# .pnp.*
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,yarn
|
||||
|
||||
.env
|
||||
node_modules
|
|
@ -0,0 +1,4 @@
|
|||
dist/
|
||||
node_modules/
|
||||
.yarn/
|
||||
examples/*/dist/
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"projectLanguage": "ts",
|
||||
"locations": {
|
||||
"base": "src",
|
||||
"arguments": "arguments",
|
||||
"commands": "commands",
|
||||
"listeners": "listeners",
|
||||
"preconditions": "preconditions"
|
||||
},
|
||||
"customFileTemplates": {
|
||||
"enabled": false,
|
||||
"location": ""
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
enableGlobalCache: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
spec: "@yarnpkg/plugin-typescript"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
|
@ -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"]
|
37
README.md
37
README.md
|
@ -1,3 +1,36 @@
|
|||
# discord-button-roles
|
||||
# Docker Sapphire Bot example
|
||||
|
||||
A Discord bot that serves one purpose: button roles, as I haven't found a good free one, that doesn't also have a million other features.
|
||||
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-sample-bot
|
||||
```
|
||||
|
||||
To test if your image works, you can run:
|
||||
|
||||
```sh
|
||||
docker run --env-file .env sapphire-sample-bot
|
||||
```
|
||||
|
||||
## 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
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
sapphire-sample-bot:
|
||||
build:
|
||||
context: .
|
||||
target: development
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./:/opt/app
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "discord-button-roles",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"author": "@sapphire",
|
||||
"license": "UNLICENSE",
|
||||
"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\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@sapphire/decorators": "^6.0.0",
|
||||
"@sapphire/discord-utilities": "^3.0.0",
|
||||
"@sapphire/discord.js-utilities": "6.0.1",
|
||||
"@sapphire/fetch": "^2.4.1",
|
||||
"@sapphire/framework": "^4.0.2",
|
||||
"@sapphire/plugin-api": "^5.0.0",
|
||||
"@sapphire/plugin-editable-commands": "^3.0.0",
|
||||
"@sapphire/plugin-logger": "^3.0.1",
|
||||
"@sapphire/plugin-subcommands": "^4.0.0",
|
||||
"@sapphire/time-utilities": "^1.7.8",
|
||||
"@sapphire/type": "^2.3.0",
|
||||
"@sapphire/utilities": "^3.11.0",
|
||||
"@skyra/env-utilities": "^1.1.0",
|
||||
"discord.js": "^14.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sapphire/prettier-config": "^1.4.5",
|
||||
"@sapphire/ts-config": "^3.3.4",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/ws": "^8.5.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.3",
|
||||
"tsc-watch": "^6.0.0",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"prettier": "@sapphire/prettier-config",
|
||||
"packageManager": "yarn@3.4.1"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { ApplyOptions } from '@sapphire/decorators';
|
||||
import { Command } from '@sapphire/framework';
|
||||
import { send } from '@sapphire/plugin-editable-commands';
|
||||
import type { Message } from 'discord.js';
|
||||
|
||||
@ApplyOptions<Command.Options>({
|
||||
description: 'ping pong'
|
||||
})
|
||||
export class UserCommand extends Command {
|
||||
public async messageRun(message: Message) {
|
||||
const msg = await send(message, 'Ping?');
|
||||
|
||||
return send(
|
||||
message,
|
||||
`Pong from Docker! Bot Latency ${Math.round(this.container.client.ws.ping)}ms. API Latency ${
|
||||
msg.createdTimestamp - message.createdTimestamp
|
||||
}ms.`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import './lib/setup';
|
||||
import { LogLevel, SapphireClient } from '@sapphire/framework';
|
||||
import { GatewayIntentBits, Partials } from 'discord.js';
|
||||
|
||||
const client = new SapphireClient({
|
||||
defaultPrefix: process.env.DEFAULT_PREFIX,
|
||||
regexPrefix: /^(hey +)?bot[,! ]/i,
|
||||
caseInsensitiveCommands: true,
|
||||
logger: {
|
||||
level: LogLevel.Debug
|
||||
},
|
||||
shards: 'auto',
|
||||
intents: [
|
||||
GatewayIntentBits.DirectMessageReactions,
|
||||
GatewayIntentBits.DirectMessages,
|
||||
GatewayIntentBits.GuildModeration,
|
||||
GatewayIntentBits.GuildEmojisAndStickers,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessageReactions,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildVoiceStates,
|
||||
GatewayIntentBits.MessageContent
|
||||
],
|
||||
partials: [Partials.Channel],
|
||||
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();
|
|
@ -0,0 +1,4 @@
|
|||
import { join } from 'path';
|
||||
|
||||
export const rootDir = join(__dirname, '..', '..');
|
||||
export const srcDir = join(rootDir, 'src');
|
|
@ -0,0 +1,20 @@
|
|||
// Unless explicitly defined, set NODE_ENV as development:
|
||||
process.env.NODE_ENV ??= 'development';
|
||||
|
||||
import '@sapphire/plugin-api/register';
|
||||
import '@sapphire/plugin-editable-commands/register';
|
||||
import '@sapphire/plugin-logger/register';
|
||||
import * as colorette from 'colorette';
|
||||
import { setup } from '@skyra/env-utilities';
|
||||
import { join } from 'path';
|
||||
import { inspect } from 'util';
|
||||
import { srcDir } from './constants';
|
||||
|
||||
// Read env var
|
||||
setup({ path: join(srcDir, '.env') });
|
||||
|
||||
// Set default inspection depth
|
||||
inspect.defaultOptions.depth = 1;
|
||||
|
||||
// Enable colorette
|
||||
colorette.createColors({ useColor: true });
|
|
@ -0,0 +1,40 @@
|
|||
import type { MessageCommandSuccessPayload } from '@sapphire/framework';
|
||||
import { Command, 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 {
|
||||
public run({ message, command }: MessageCommandSuccessPayload) {
|
||||
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)}]`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Listener } from '@sapphire/framework';
|
||||
import type { Message } from 'discord.js';
|
||||
|
||||
export class UserEvent extends Listener {
|
||||
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.');
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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}.`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "@sapphire/ts-config",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in New Issue