commit
9914b3a4d4
27 changed files with 236 additions and 106 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
.vscode
|
||||
.husky
|
||||
.github
|
||||
.cspell
|
||||
.env
|
||||
node_modules
|
30
.env.example
Normal file
30
.env.example
Normal file
|
@ -0,0 +1,30 @@
|
|||
# THIS FILE SHOULD BE INSIDE OF build/
|
||||
|
||||
# Discord
|
||||
DISCORD_TOKEN=""
|
||||
DISCORD_CLIENT_ID=""
|
||||
DISCORD_GUILD_ID=""
|
||||
|
||||
# Database
|
||||
MONGO_URL="mongodb+srv://username:password@host/database?retryWrites=true&w=majority"
|
||||
|
||||
# Encryption
|
||||
ENCRYPTION_ALGORITHM="aes-256-ctr"
|
||||
ENCRYPTION_SECRET="A RANDOM STRING WITH LENGTH OF 32"
|
||||
|
||||
#Embed
|
||||
EMEBD_COLOR_SUCCESS="#22bb33"
|
||||
EMBED_COLOR_WAIT="#f0ad4e"
|
||||
EMBED_COLOR_ERROR="#bb2124"
|
||||
EMBED_FOOTER_TEXT="https://github.com/ZynerOrg/xyter"
|
||||
EMBED_FOOTER_ICON="https://github.com/ZynerOrg.png"
|
||||
|
||||
# Log
|
||||
LOG_LEVEL="silly"
|
||||
|
||||
# Reputation
|
||||
REPUTATION_TIMEOUT="86400"
|
||||
|
||||
# Bot Hoster
|
||||
BOT_HOSTER_NAME="Zyner"
|
||||
BOT_HOSTER_URL="https://xyter.zyner.org/customization/change-hoster"
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -140,3 +140,8 @@ dist
|
|||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
|
||||
# Docker
|
||||
database
|
||||
docker-compose.local.yml
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -22,5 +22,8 @@
|
|||
"addWords": true,
|
||||
"scope": "workspace"
|
||||
}
|
||||
},
|
||||
"[dotenv]": {
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
}
|
||||
}
|
||||
|
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM node:16
|
||||
|
||||
LABEL maintainer="xyter@zyner.org"
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY package* .
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npx -y tsc
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN cp -r /build/build/* .
|
||||
RUN cp -r /build/node_modules .
|
||||
|
||||
CMD [ "node", "." ]
|
37
docker-compose.dev.yml
Normal file
37
docker-compose.dev.yml
Normal file
|
@ -0,0 +1,37 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
depends_on:
|
||||
- mongodb
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DISCORD_TOKEN=DISCORD_TOKEN
|
||||
- DISCORD_CLIENT_ID=DISCORD_CLIENT_ID
|
||||
- DISCORD_GUILD_ID=DISCORD_GUILD_ID
|
||||
- MONGO_URL=mongodb://MONGO_USER:MONGO_PASS@mongodb:27017/admin?retryWrites=true&w=majority
|
||||
- ENCRYPTION_ALGORITHM=ENCRYPTION_ALGORITHM
|
||||
- ENCRYPTION_SECRET=ENCRYPTION_SECRET
|
||||
- EMEBD_COLOR_SUCCESS=EMEBD_COLOR_SUCCESS
|
||||
- EMBED_COLOR_WAIT=EMBED_COLOR_WAIT
|
||||
- EMBED_COLOR_ERROR=EMBED_COLOR_ERROR
|
||||
- EMBED_FOOTER_TEXT=EMBED_FOOTER_TEXT
|
||||
- EMBED_FOOTER_ICON=EMBED_FOOTER_ICON
|
||||
- LOG_LEVEL=LOG_LEVEL
|
||||
- REPUTATION_TIMEOUT=REPUTATION_TIMEOUT
|
||||
- BOT_HOSTER_NAME=BOT_HOSTER_NAME
|
||||
- BOT_HOSTER_URL=BOT_HOSTER_URL
|
||||
- NODE_ENV=development
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: MONGO_USER
|
||||
MONGO_INITDB_ROOT_PASSWORD: MONGO_PASS
|
||||
volumes:
|
||||
- ./database:/data/db
|
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
|
@ -0,0 +1,37 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
depends_on:
|
||||
- mongodb
|
||||
image: zyner/xyter:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DISCORD_TOKEN=DISCORD_TOKEN
|
||||
- DISCORD_CLIENT_ID=DISCORD_CLIENT_ID
|
||||
- DISCORD_GUILD_ID=DISCORD_GUILD_ID
|
||||
- MONGO_URL=mongodb://MONGO_USER:MONGO_PASS@mongodb:27017/admin?retryWrites=true&w=majority
|
||||
- ENCRYPTION_ALGORITHM=ENCRYPTION_ALGORITHM
|
||||
- ENCRYPTION_SECRET=ENCRYPTION_SECRET
|
||||
- EMEBD_COLOR_SUCCESS=EMEBD_COLOR_SUCCESS
|
||||
- EMBED_COLOR_WAIT=EMBED_COLOR_WAIT
|
||||
- EMBED_COLOR_ERROR=EMBED_COLOR_ERROR
|
||||
- EMBED_FOOTER_TEXT=EMBED_FOOTER_TEXT
|
||||
- EMBED_FOOTER_ICON=EMBED_FOOTER_ICON
|
||||
- LOG_LEVEL=LOG_LEVEL
|
||||
- REPUTATION_TIMEOUT=REPUTATION_TIMEOUT
|
||||
- BOT_HOSTER_NAME=BOT_HOSTER_NAME
|
||||
- BOT_HOSTER_URL=BOT_HOSTER_URL
|
||||
- NODE_ENV=production
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: MONGO_USER
|
||||
MONGO_INITDB_ROOT_PASSWORD: MONGO_PASS
|
||||
volumes:
|
||||
- ./database:/data/db
|
|
@ -21,6 +21,9 @@
|
|||
"url": "https://github.com/ZynerOrg/xyter.git"
|
||||
},
|
||||
"author": "Vermium Sifell <vermium@zyner.org> (https://zyner.org)",
|
||||
"contributors": [
|
||||
"Joshua Schmitt <me@jqshuv.xyz> (https://jqshuv.xyz)"
|
||||
],
|
||||
"license": "GPL-3.0-only",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ZynerOrg/xyter/issues",
|
||||
|
@ -34,6 +37,7 @@
|
|||
"chance": "^1.1.8",
|
||||
"common": "^0.2.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"discord-api-types": "^0.37.0",
|
||||
"discord.js": "^14.0.0",
|
||||
"i18n": "^0.15.0",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// MongoDB connection string
|
||||
export const url =
|
||||
"mongodb+srv://username:password@server/database?retryWrites=true&w=majority";
|
|
@ -1,14 +0,0 @@
|
|||
import { Intents } from "discord.js"; // discord.js
|
||||
|
||||
// Discord API token
|
||||
export const token = "";
|
||||
|
||||
// Discord API id
|
||||
export const clientId = "";
|
||||
|
||||
// Discord API intents
|
||||
export const intents = [
|
||||
Intents.FLAGS.GUILDS,
|
||||
Intents.FLAGS.GUILD_MESSAGES,
|
||||
Intents.FLAGS.GUILD_MEMBERS,
|
||||
];
|
|
@ -1,17 +0,0 @@
|
|||
// Dependencies
|
||||
import { ColorResolvable } from "discord.js";
|
||||
|
||||
// Color for successfully actions
|
||||
export const successColor: ColorResolvable = "#22bb33";
|
||||
|
||||
// Color for waiting actions
|
||||
export const waitColor: ColorResolvable = "#f0ad4e";
|
||||
|
||||
// Color for error actions
|
||||
export const errorColor: ColorResolvable = "#bb2124";
|
||||
|
||||
// Footer text
|
||||
export const footerText = "https://github.com/ZynerOrg/xyter";
|
||||
|
||||
// Footer icon
|
||||
export const footerIcon = "https://github.com/ZynerOrg.png";
|
|
@ -1,5 +0,0 @@
|
|||
// Encryption algorithm
|
||||
export const algorithm = "aes-256-ctr";
|
||||
|
||||
// Encryption secret (strictly 32 length)
|
||||
export const secretKey = "";
|
|
@ -1,14 +0,0 @@
|
|||
// Development features
|
||||
export const devMode = false;
|
||||
|
||||
// Development guild
|
||||
export const guildId = "";
|
||||
|
||||
// Hoster name
|
||||
export const hosterName = "someone";
|
||||
|
||||
// Hoster Url
|
||||
export const hosterUrl = "https://xyter.zyner.org/customization/change-hoster";
|
||||
|
||||
// Winston log level
|
||||
export const logLevel = "info";
|
|
@ -1,2 +0,0 @@
|
|||
// Timeout between repute someone (seconds)
|
||||
export const timeout = 86400; // One day
|
|
@ -1,12 +1,8 @@
|
|||
import { token, clientId } from "../../config/discord";
|
||||
import { devMode, guildId } from "../../config/other";
|
||||
|
||||
import logger from "../../logger";
|
||||
import { Client } from "discord.js";
|
||||
import { REST } from "@discordjs/rest";
|
||||
import { Routes } from "discord-api-types/v9";
|
||||
import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10";
|
||||
|
||||
import { ICommand } from "../../interfaces/Command";
|
||||
|
||||
export default async (client: Client) => {
|
||||
|
@ -32,10 +28,10 @@ export default async (client: Client) => {
|
|||
throw new Error(`Could not gather command list: ${error}`);
|
||||
});
|
||||
|
||||
const rest = new REST({ version: "9" }).setToken(token);
|
||||
const rest = new REST({ version: "9" }).setToken(process.env.DISCORD_TOKEN);
|
||||
|
||||
await rest
|
||||
.put(Routes.applicationCommands(clientId), {
|
||||
.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), {
|
||||
body: commandList,
|
||||
})
|
||||
.then(async () => {
|
||||
|
@ -45,11 +41,17 @@ export default async (client: Client) => {
|
|||
logger.error(`${error}`);
|
||||
});
|
||||
|
||||
if (devMode) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
await rest
|
||||
.put(Routes.applicationGuildCommands(clientId, guildId), {
|
||||
body: commandList,
|
||||
})
|
||||
.put(
|
||||
Routes.applicationGuildCommands(
|
||||
process.env.DISCORD_CLIENT_ID,
|
||||
process.env.DISCORD_GUILD_ID
|
||||
),
|
||||
{
|
||||
body: commandList,
|
||||
}
|
||||
)
|
||||
.then(async () => logger.info(`Finished updating guild command list.`))
|
||||
.catch(async (error) => {
|
||||
logger.error(`${error}`);
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
// Dependencies
|
||||
import { Client } from "discord.js";
|
||||
|
||||
import logger from "../../logger";
|
||||
|
||||
// Configuration
|
||||
import { devMode, guildId } from "../../config/other";
|
||||
|
||||
export default async (client: Client) => {
|
||||
if (!devMode) {
|
||||
return client?.application?.commands?.set([], guildId).then(async () => {
|
||||
return logger.verbose(`Development mode is disabled.`);
|
||||
});
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return client?.application?.commands
|
||||
?.set([], process.env.DISCORD_GUILD_ID)
|
||||
.then(async () => {
|
||||
return logger.verbose(`Development mode is disabled.`);
|
||||
});
|
||||
}
|
||||
|
||||
return logger.info(`Development mode is enabled.`);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import crypto from "crypto";
|
||||
|
||||
import { secretKey, algorithm } from "../../config/encryption";
|
||||
|
||||
import { IEncryptionData } from "../../interfaces/EncryptionData";
|
||||
|
||||
const iv = crypto.randomBytes(16);
|
||||
|
||||
const encrypt = (text: crypto.BinaryLike): IEncryptionData => {
|
||||
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
|
||||
const cipher = crypto.createCipheriv(
|
||||
process.env.ENCRYPTION_ALGORITHM,
|
||||
process.env.ENCRYPTION_SECRET,
|
||||
iv
|
||||
);
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||
|
||||
return {
|
||||
|
@ -18,8 +19,8 @@ const encrypt = (text: crypto.BinaryLike): IEncryptionData => {
|
|||
|
||||
const decrypt = (hash: IEncryptionData) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
secretKey,
|
||||
process.env.ENCRYPTION_ALGORITHM,
|
||||
process.env.ENCRYPTION_SECRET,
|
||||
Buffer.from(hash.iv, "hex")
|
||||
);
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { footerText, footerIcon } from "../../config/embed";
|
||||
import { MessageEmbed } from "discord.js";
|
||||
|
||||
export default new MessageEmbed()
|
||||
.setFooter({
|
||||
text: footerText,
|
||||
iconURL: footerIcon,
|
||||
text: process.env.EMBED_FOOTER_TEXT,
|
||||
iconURL: process.env.EMBED_FOOTER_ICON,
|
||||
})
|
||||
.setTimestamp(new Date());
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
import guildSchema from "../../models/guild";
|
||||
import * as embedConfig from "../../config/embed";
|
||||
|
||||
import { Guild } from "discord.js";
|
||||
|
||||
export default async (guild?: Guild | null) => {
|
||||
const {
|
||||
EMBED_COLOR_SUCCESS,
|
||||
EMBED_COLOR_WAIT,
|
||||
EMBED_COLOR_ERROR,
|
||||
EMBED_FOOTER_TEXT,
|
||||
EMBED_FOOTER_ICON,
|
||||
} = process.env;
|
||||
|
||||
const defaultEmbedConfig = {
|
||||
successColor: EMBED_COLOR_SUCCESS,
|
||||
waitColor: EMBED_COLOR_WAIT,
|
||||
errorColor: EMBED_COLOR_ERROR,
|
||||
footerText: EMBED_FOOTER_TEXT,
|
||||
footerIcon: EMBED_FOOTER_ICON,
|
||||
};
|
||||
if (!guild) {
|
||||
return { ...embedConfig };
|
||||
return defaultEmbedConfig;
|
||||
}
|
||||
|
||||
const guildConfig = await guildSchema.findOne({ guildId: guild.id });
|
||||
if (!guildConfig) {
|
||||
return {
|
||||
...embedConfig,
|
||||
};
|
||||
return defaultEmbedConfig;
|
||||
}
|
||||
return guildConfig.embeds;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { token, intents } from "./config/discord";
|
||||
|
||||
import { Client, Collection } from "discord.js"; // discord.js
|
||||
|
||||
import * as managers from "./managers";
|
||||
|
@ -8,7 +6,7 @@ import * as managers from "./managers";
|
|||
const main = async () => {
|
||||
// Initiate client object
|
||||
const client = new Client({
|
||||
intents,
|
||||
intents: ["GUILDS", "GUILD_MEMBERS", "GUILD_MESSAGES"],
|
||||
});
|
||||
|
||||
// Create command collection
|
||||
|
@ -17,7 +15,7 @@ const main = async () => {
|
|||
await managers.start(client);
|
||||
|
||||
// Authorize with Discord's API
|
||||
await client.login(token);
|
||||
await client.login(process.env.DISCORD_TOKEN);
|
||||
};
|
||||
|
||||
main();
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import winston from "winston";
|
||||
import "winston-daily-rotate-file";
|
||||
|
||||
import { logLevel } from "../config/other";
|
||||
|
||||
const { combine, timestamp, printf, errors, colorize, align, json } =
|
||||
winston.format;
|
||||
|
||||
export default winston.createLogger({
|
||||
level: logLevel || "info",
|
||||
level: process.env.LOG_LEVEL || "info",
|
||||
transports: [
|
||||
new winston.transports.DailyRotateFile({
|
||||
filename: "logs/combined-%DATE%.log",
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
import mongoose from "mongoose";
|
||||
import { url } from "../../config/database";
|
||||
import logger from "../../logger";
|
||||
|
||||
export const connect = async () => {
|
||||
await mongoose.connect(url);
|
||||
await mongoose
|
||||
.connect(process.env.MONGO_URL)
|
||||
.then(async (connection) => {
|
||||
logger.info(`💾 Connected to database: ${connection.connection.name}`);
|
||||
})
|
||||
.catch(async (e) => {
|
||||
logger.error("💾 Could not connect to database", e);
|
||||
});
|
||||
|
||||
mongoose.connection.on("error", async (error) => {
|
||||
logger.error(`💾 ${error}`);
|
||||
});
|
||||
|
||||
mongoose.connection.on("warn", async (warning) => {
|
||||
logger.warn(`💾 ${warning}`);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import "dotenv/config";
|
||||
import { Client } from "discord.js";
|
||||
|
||||
import * as database from "./database";
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
// Dependencies
|
||||
import {
|
||||
CommandInteraction,
|
||||
Permissions,
|
||||
} from "discord.js";
|
||||
import { CommandInteraction, Permissions } from "discord.js";
|
||||
|
||||
// Configurations
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { CommandInteraction } from "discord.js";
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
import { timeout } from "../../../../../config/reputation";
|
||||
import logger from "../../../../../logger";
|
||||
import fetchUser from "../../../../../helpers/fetchUser";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
|
@ -53,7 +52,7 @@ export default {
|
|||
await noSelfReputation(optionTarget, user);
|
||||
|
||||
// Check if user is on cooldown otherwise create one
|
||||
await cooldown.command(interaction, timeout);
|
||||
await cooldown.command(interaction, process.env.REPUTATION_TIMEOUT);
|
||||
|
||||
switch (optionType) {
|
||||
case "positive":
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
// Configurations
|
||||
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
|
||||
|
||||
import { hosterName, hosterUrl } from "../../../../../config/other";
|
||||
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
|
||||
|
||||
// Function
|
||||
|
@ -45,10 +44,10 @@ export default {
|
|||
.setEmoji("💬")
|
||||
.setURL("https://discord.zyner.org"),
|
||||
new MessageButton()
|
||||
.setLabel(`Hosted by ${hosterName}`)
|
||||
.setLabel(`Hosted by ${process.env.BOT_HOSTER_NAME}`)
|
||||
.setStyle("LINK")
|
||||
.setEmoji("⚒️")
|
||||
.setURL(`${hosterUrl}`)
|
||||
.setURL(`${process.env.BOT_HOSTER_URL}`)
|
||||
);
|
||||
|
||||
const interactionEmbed = {
|
||||
|
|
26
src/types/common/environment.d.ts
vendored
Normal file
26
src/types/common/environment.d.ts
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { Snowflake, ColorResolvable } from "discord.js";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
MONGO_URL: string;
|
||||
DISCORD_TOKEN: string;
|
||||
DISCORD_CLIENT_ID: Snowflake;
|
||||
DISCORD_GUILD_ID: Snowflake;
|
||||
DEVELOPMENT_MODE: boolean;
|
||||
ENCRYPTION_ALGORITHM: string;
|
||||
ENCRYPTION_SECRET: string;
|
||||
EMBED_COLOR_SUCCESS: ColorResolvable;
|
||||
EMBED_COLOR_WAIT: ColorResolvable;
|
||||
EMBED_COLOR_ERROR: ColorResolvable;
|
||||
EMBED_FOOTER_TEXT: string;
|
||||
EMBED_FOOTER_ICON: string;
|
||||
LOG_LEVEL: string;
|
||||
REPUTATION_TIMEOUT: number;
|
||||
BOT_HOSTER_NAME: string;
|
||||
BOT_HOSTER_URL: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
Loading…
Add table
Reference in a new issue