V1.0.0-beta.1 #1
							
								
								
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
FROM node:18-alpine AS builder
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
COPY build/package*.json ./
 | 
			
		||||
RUN apk add --no-cache python3 make g++
 | 
			
		||||
RUN npm install --omit=dev
 | 
			
		||||
 | 
			
		||||
FROM node:18-alpine AS app
 | 
			
		||||
WORKDIR /usr/src/app
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
COPY --from=builder node_modules ./node_modules
 | 
			
		||||
COPY build/ .
 | 
			
		||||
VOLUME [ "/usr/src/app/data" ]
 | 
			
		||||
CMD ["node", "--enable-source-maps", "index.js"]
 | 
			
		||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -1,2 +1,16 @@
 | 
			
		||||
# eu.astrogd.white-leopard
 | 
			
		||||
A Discord bot that checks Discord channel names for banned words and prevents renaming of them
 | 
			
		||||
 | 
			
		||||
## Environment variables
 | 
			
		||||
| Name        | Description                                                   | Required | Example             |
 | 
			
		||||
| :---------- | :------------------------------------------------------------ | :------: | :------------------ |
 | 
			
		||||
| TOKEN       | Discord bot token to log into the API with                    |   ▶️/🚀    | NzYzMDP3MzE1Mzky... |
 | 
			
		||||
| DB_HOST     | Hostname of the database                                      |    ▶️     | 127.0.0.1:3546      |
 | 
			
		||||
| DB_USERNAME | Username of the database                                      |    ▶️     | root                |
 | 
			
		||||
| DB_PASSWORD | Password of the database                                      |    ▶️     | abc123              |
 | 
			
		||||
| DB_DATABASE | Database name                                                 |    ▶️     | white-leopard       |
 | 
			
		||||
| CLIENT_ID   | Client ID of the Discord appication associated with the token |    🚀     | 763035392692274     |
 | 
			
		||||
 | 
			
		||||
### Icon explanation:
 | 
			
		||||
- ▶️ = Required in runtime
 | 
			
		||||
- 🚀 = Required during CI/CD
 | 
			
		||||
							
								
								
									
										33
									
								
								ci/deploy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ci/deploy.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
// Dotenv initialization
 | 
			
		||||
import path from "path";
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
dotenv.config({ path: path.join(__dirname, "../.env") });
 | 
			
		||||
 | 
			
		||||
// Environment checking
 | 
			
		||||
const TOKEN = process.env["TOKEN"];
 | 
			
		||||
const CLIENT_ID = process.env["CLIENT_ID"];
 | 
			
		||||
 | 
			
		||||
if (!TOKEN) throw new ReferenceError("Environment variable TOKEN is missing");
 | 
			
		||||
if (!CLIENT_ID) throw new ReferenceError("Environment variable CLIENT_ID is missing");
 | 
			
		||||
 | 
			
		||||
// Deployment
 | 
			
		||||
import { REST, Routes } from "discord.js";
 | 
			
		||||
import { array as commands } from "../src/commands/ci";
 | 
			
		||||
 | 
			
		||||
const API = new REST({ version: "10" }).setToken(TOKEN);
 | 
			
		||||
 | 
			
		||||
(async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        console.log("Start deploying slash commands globally");
 | 
			
		||||
 | 
			
		||||
        const data = await API.put(
 | 
			
		||||
            Routes.applicationCommands(CLIENT_ID),
 | 
			
		||||
            { body: commands },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        console.log(`Successfully deployed ${(data as any).length} commands`);
 | 
			
		||||
    }
 | 
			
		||||
    catch (e) {
 | 
			
		||||
        console.error(e);
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										33
									
								
								ci/devDeploy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ci/devDeploy.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
// Dotenv initialization
 | 
			
		||||
import path from "path";
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
dotenv.config({ path: path.join(__dirname, "../.env") });
 | 
			
		||||
 | 
			
		||||
// Environment checking
 | 
			
		||||
const TOKEN = process.env["TOKEN"];
 | 
			
		||||
const CLIENT_ID = process.env["CLIENT_ID"];
 | 
			
		||||
 | 
			
		||||
if (!TOKEN) throw new ReferenceError("Environment variable TOKEN is missing");
 | 
			
		||||
if (!CLIENT_ID) throw new ReferenceError("Environment variable CLIENT_ID is missing");
 | 
			
		||||
 | 
			
		||||
// Deployment
 | 
			
		||||
import { REST, Routes } from "discord.js";
 | 
			
		||||
import { array as commands } from "../src/commands/ci";
 | 
			
		||||
 | 
			
		||||
const API = new REST({ version: "10" }).setToken(TOKEN);
 | 
			
		||||
 | 
			
		||||
(async () => {
 | 
			
		||||
    try {
 | 
			
		||||
        console.log("Start deploying slash commands for Dev guild");
 | 
			
		||||
 | 
			
		||||
        const data = await API.put(
 | 
			
		||||
            Routes.applicationGuildCommands(CLIENT_ID, "853049355984437269"),
 | 
			
		||||
            { body: commands },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        console.log(`Successfully deployed ${(data as any).length} commands`);
 | 
			
		||||
    }
 | 
			
		||||
    catch (e) {
 | 
			
		||||
        console.error(e);
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										25
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
version: "2"
 | 
			
		||||
services:
 | 
			
		||||
  app:
 | 
			
		||||
    image: astrogd/white-leopard
 | 
			
		||||
    build: ./
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - database
 | 
			
		||||
    restart: always
 | 
			
		||||
    environment:
 | 
			
		||||
      - TOKEN=$TOKEN
 | 
			
		||||
      - DB_HOST=database:5432
 | 
			
		||||
      - DB_USERNAME=$DB_USERNAME
 | 
			
		||||
      - DB_PASSWORD=$DB_PASSWORD
 | 
			
		||||
      - DB_DATABASE=$DB_DATABASE
 | 
			
		||||
  database:
 | 
			
		||||
    image: postgres:latest
 | 
			
		||||
    restart: always
 | 
			
		||||
    ports:
 | 
			
		||||
      - 5432:5432
 | 
			
		||||
    environment:
 | 
			
		||||
      - POSTGRES_USER=$DB_USERNAME
 | 
			
		||||
      - POSTGRES_PASSWORD=$DB_PASSWORD
 | 
			
		||||
      - POSTGRES_DB=$DB_DATABASE
 | 
			
		||||
    expose: 
 | 
			
		||||
      - 5432
 | 
			
		||||
							
								
								
									
										4
									
								
								index.ts
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								index.ts
									
									
									
									
									
								
							@@ -1,9 +1,11 @@
 | 
			
		||||
import { config } from "dotenv";
 | 
			
		||||
import swapConsole from "./src/tools/consoleSwapper";
 | 
			
		||||
import "./src/client";
 | 
			
		||||
 | 
			
		||||
config();
 | 
			
		||||
swapConsole();
 | 
			
		||||
 | 
			
		||||
import "./src/client";
 | 
			
		||||
 | 
			
		||||
function shutdown() {
 | 
			
		||||
    console.log("Shutdown request received");
 | 
			
		||||
    process.exit();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1115
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1115
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							@@ -4,7 +4,15 @@
 | 
			
		||||
  "description": "A Discord bot that checks channel names for blacklisted words and reverts the changes if necessary",
 | 
			
		||||
  "main": "build/index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1"
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1",
 | 
			
		||||
    "build": "tsc && shx cp package-lock.json build/package-lock.json",
 | 
			
		||||
    "deploy-dev": "ts-node ci/devDeploy.ts",
 | 
			
		||||
    "start": "npm run build && npm run deploy-dev && docker-compose up --no-start && docker compose start database && node --enable-source-maps .",
 | 
			
		||||
    "migration:create": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate -d src/data/dataSource.ts -p src/data/migrations/data",
 | 
			
		||||
    "migration:run": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:run -d src/data/dataSource.ts",
 | 
			
		||||
    "migration:revert": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:revert -d src/data/dataSource.ts",
 | 
			
		||||
    "migration:show": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:show -d src/data/dataSource.ts",
 | 
			
		||||
    "migration:check": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate --check -d src/data/dataSource.ts src/data/migrations/data"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
@@ -19,11 +27,15 @@
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/fs-extra": "^9.0.13",
 | 
			
		||||
    "@types/node": "^18.11.9",
 | 
			
		||||
    "shx": "^0.3.4",
 | 
			
		||||
    "ts-node": "^10.9.1",
 | 
			
		||||
    "typescript": "^4.9.3"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "discord.js": "^14.6.0",
 | 
			
		||||
    "dotenv": "^16.0.3",
 | 
			
		||||
    "fs-extra": "^10.1.0"
 | 
			
		||||
    "fs-extra": "^10.1.0",
 | 
			
		||||
    "pg": "^8.8.0",
 | 
			
		||||
    "typeorm": "^0.3.10"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,4 +15,6 @@ client.on("ready", () => {
 | 
			
		||||
    console.log(`Connected to Discord API. Bot account is ${client.user?.tag} (${client.user?.id})`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default client;
 | 
			
		||||
export default client;
 | 
			
		||||
 | 
			
		||||
import "../commands";
 | 
			
		||||
							
								
								
									
										7
									
								
								src/commands/ci.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/commands/ci.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import * as notification from "./notification";
 | 
			
		||||
 | 
			
		||||
const array = [notification.builder.toJSON()];
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    array
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
import { ChatInputCommandInteraction, Collection, Events, SlashCommandBuilder } from "discord.js";
 | 
			
		||||
import * as notification from "./notification";
 | 
			
		||||
import client from "../client";
 | 
			
		||||
import getDefaultEmbed from "../tools/defaultEmbeds";
 | 
			
		||||
 | 
			
		||||
const commands = new Collection<string, { builder: SlashCommandBuilder, execute: (interaction: ChatInputCommandInteraction) => Promise<void> }>();
 | 
			
		||||
commands.set(notification.builder.name, notification);
 | 
			
		||||
 | 
			
		||||
client.on(Events.InteractionCreate, async (interaction) => {
 | 
			
		||||
    if (!interaction.isChatInputCommand()) return;
 | 
			
		||||
 | 
			
		||||
    const command = commands.get(interaction.commandName);
 | 
			
		||||
 | 
			
		||||
    if (!command) {
 | 
			
		||||
        console.error(`No command matching ${interaction.commandName} was found`);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        await command.execute(interaction);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        
 | 
			
		||||
        if (e instanceof Error && "stack" in e) {
 | 
			
		||||
            console.error(e.stack);
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error(e)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const embed = getDefaultEmbed();
 | 
			
		||||
        embed.setTitle("Something went wrong");
 | 
			
		||||
        embed.setDescription("An unexpected error occurred while processing your command. Please contact support if the problem persists");
 | 
			
		||||
        embed.setColor(0xD01B15);
 | 
			
		||||
        await interaction.reply({
 | 
			
		||||
            embeds: [embed],
 | 
			
		||||
            ephemeral: true
 | 
			
		||||
        }).catch();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, ChatInputCommandInteraction, NewsChannel, TextBasedChannel, CategoryChannel, StageChannel, TextChannel, PrivateThreadChannel, PublicThreadChannel, VoiceChannel, APIInteractionDataResolvedChannel, ForumChannel } from "discord.js";
 | 
			
		||||
import { getSuccessEmbed } from "../tools/defaultEmbeds";
 | 
			
		||||
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, ChatInputCommandInteraction, NewsChannel, TextBasedChannel, CategoryChannel, StageChannel, TextChannel, PrivateThreadChannel, PublicThreadChannel, VoiceChannel, APIInteractionDataResolvedChannel, ForumChannel, GuildBasedChannel } from "discord.js";
 | 
			
		||||
import getDefaultEmbed, { getSuccessEmbed } from "../tools/defaultEmbeds";
 | 
			
		||||
import { database, GuildSetting } from "../data";
 | 
			
		||||
import client from "../client";
 | 
			
		||||
 | 
			
		||||
const builder = new SlashCommandBuilder();
 | 
			
		||||
builder.setName("logchannel");
 | 
			
		||||
@@ -8,37 +10,119 @@ builder.setDMPermission(false);
 | 
			
		||||
builder.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels);
 | 
			
		||||
builder.addChannelOption((option) => {
 | 
			
		||||
    option.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement);
 | 
			
		||||
    option.setName("Channel");
 | 
			
		||||
    option.setName("channel");
 | 
			
		||||
    option.setDescription("The channel to send notifications to");
 | 
			
		||||
    option.setRequired(true);
 | 
			
		||||
    option.setRequired(false);
 | 
			
		||||
    return option;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const execute = (interaction: ChatInputCommandInteraction) => {
 | 
			
		||||
    const channel:TextBasedChannel = getTextBasedChannel(interaction.options.getChannel("Channel", true));
 | 
			
		||||
 | 
			
		||||
    //TODO Save channel in config
 | 
			
		||||
 | 
			
		||||
    const embed = getSuccessEmbed();
 | 
			
		||||
    embed.setDescription(`Log channel has been set to <#${channel.id}>`);
 | 
			
		||||
    interaction.reply({
 | 
			
		||||
        embeds: [embed],
 | 
			
		||||
        ephemeral: true
 | 
			
		||||
async function getGuildSetting(guildID: string): Promise<GuildSetting> {
 | 
			
		||||
    let guildSetting = await database.getRepository(GuildSetting).findOne({
 | 
			
		||||
        where: {
 | 
			
		||||
            id: guildID
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const logMessage = getSuccessEmbed();
 | 
			
		||||
    logMessage.setDescription("This channel has been selected as the log channel");
 | 
			
		||||
    logMessage.addFields({
 | 
			
		||||
    if (!guildSetting) {
 | 
			
		||||
        guildSetting = new GuildSetting();
 | 
			
		||||
        guildSetting.id = guildID;
 | 
			
		||||
        guildSetting.isPremiumUntil = null;
 | 
			
		||||
        guildSetting.notificationChannelID = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return guildSetting;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getGuildChannel(guildID: string, channelID: string): Promise<GuildBasedChannel | null> {
 | 
			
		||||
    const guild = await client.guilds.fetch(guildID);
 | 
			
		||||
    if (!guild) return null;
 | 
			
		||||
 | 
			
		||||
    const channel = await guild.channels.fetch(channelID);
 | 
			
		||||
    return channel;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function resetNotificationChannel(guildSetting: GuildSetting, interaction: ChatInputCommandInteraction): Promise<void> {
 | 
			
		||||
    const logChannel = guildSetting.notificationChannelID ? await getGuildChannel(guildSetting.id, guildSetting.notificationChannelID) : null;
 | 
			
		||||
    
 | 
			
		||||
    guildSetting.notificationChannelID = null;
 | 
			
		||||
    await database.getRepository(GuildSetting).save(guildSetting);
 | 
			
		||||
 | 
			
		||||
    const logEmbed = getDefaultEmbed();
 | 
			
		||||
    logEmbed.setTitle("Settings changed");
 | 
			
		||||
    logEmbed.setDescription("Log channel has been disabled");
 | 
			
		||||
    logEmbed.addFields({
 | 
			
		||||
        name: "This action was performed by",
 | 
			
		||||
        value: `${interaction.user.tag} (${interaction.user.id})`
 | 
			
		||||
    });
 | 
			
		||||
    channel.send({
 | 
			
		||||
        embeds: [logMessage]
 | 
			
		||||
    
 | 
			
		||||
    if (logChannel && logChannel.isTextBased()) {
 | 
			
		||||
        logChannel.send({
 | 
			
		||||
            embeds: [logEmbed]
 | 
			
		||||
        }).catch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const embed = getSuccessEmbed();
 | 
			
		||||
    embed.setDescription("Log channel has been disabled");
 | 
			
		||||
    interaction.reply({
 | 
			
		||||
        embeds: [embed],
 | 
			
		||||
        ephemeral: true
 | 
			
		||||
    }).catch();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const execute = async (interaction: ChatInputCommandInteraction) => {
 | 
			
		||||
    if (!interaction.guildId) throw new Error("Command can only be used inside a guild");
 | 
			
		||||
 | 
			
		||||
    const optionVal = interaction.options.getChannel("channel", false);
 | 
			
		||||
    const guildSetting = await getGuildSetting(interaction.guildId);
 | 
			
		||||
 | 
			
		||||
    if (!optionVal) return await resetNotificationChannel(guildSetting, interaction);
 | 
			
		||||
 | 
			
		||||
    const channel = getTextBasedChannel(optionVal);
 | 
			
		||||
    if (guildSetting.notificationChannelID) {
 | 
			
		||||
        const oldLogChannel = await getGuildChannel(guildSetting.id, guildSetting.notificationChannelID);
 | 
			
		||||
        const embed = getDefaultEmbed();
 | 
			
		||||
        embed.setTitle("Settings changed");
 | 
			
		||||
        embed.setDescription(`Log channel has been switched to <#${channel.id}>`);
 | 
			
		||||
        embed.addFields({
 | 
			
		||||
            name: "This action was performed by",
 | 
			
		||||
            value: `${interaction.user.tag} (${interaction.user.id})`
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (oldLogChannel && oldLogChannel.isTextBased()) {
 | 
			
		||||
            oldLogChannel.send({
 | 
			
		||||
                embeds: [embed]
 | 
			
		||||
            }).catch();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    guildSetting.notificationChannelID = channel.id;
 | 
			
		||||
    await database.getRepository(GuildSetting).save(guildSetting);
 | 
			
		||||
 | 
			
		||||
    const embed = getDefaultEmbed();
 | 
			
		||||
    embed.setTitle("Settings changed");
 | 
			
		||||
    embed.setDescription("This channel has been set as the log channel");
 | 
			
		||||
    embed.addFields({
 | 
			
		||||
        name: "This action was performed by",
 | 
			
		||||
        value: `${interaction.user.tag} (${interaction.user.id})`
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    channel.send({
 | 
			
		||||
        embeds: [ embed ]
 | 
			
		||||
    }).catch();
 | 
			
		||||
 | 
			
		||||
    const reply = getSuccessEmbed();
 | 
			
		||||
    reply.setDescription(`Log channel was set to <#${channel.id}>`);
 | 
			
		||||
 | 
			
		||||
    interaction.reply({
 | 
			
		||||
        embeds: [ reply ],
 | 
			
		||||
        ephemeral: true
 | 
			
		||||
    }).catch();
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getTextBasedChannel(channel: CategoryChannel | NewsChannel | StageChannel | TextChannel | PrivateThreadChannel | PublicThreadChannel<boolean> | VoiceChannel | ForumChannel | APIInteractionDataResolvedChannel): TextBasedChannel {
 | 
			
		||||
    if (channel.type === ChannelType.DM || channel.type === ChannelType.GroupDM || channel.type === ChannelType.GuildAnnouncement || channel.type === ChannelType.GuildText || channel.type === ChannelType.PublicThread || channel.type === ChannelType.PrivateThread || channel.type === ChannelType.GuildVoice) {
 | 
			
		||||
    if (channel.type === ChannelType.GuildAnnouncement || channel.type === ChannelType.GuildText || channel.type === ChannelType.PublicThread || channel.type === ChannelType.PrivateThread || channel.type === ChannelType.GuildVoice) {
 | 
			
		||||
        return channel as TextBasedChannel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								src/data/dataSource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/data/dataSource.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import path from "path";
 | 
			
		||||
import { DataSource } from "typeorm";
 | 
			
		||||
import { config } from "dotenv";
 | 
			
		||||
import { Badword, GuildSetting } from "./model";
 | 
			
		||||
 | 
			
		||||
config();
 | 
			
		||||
 | 
			
		||||
const host = process.env["DB_HOST"];
 | 
			
		||||
const username = process.env["DB_USERNAME"];
 | 
			
		||||
const password = process.env["DB_PASSWORD"];
 | 
			
		||||
const database = process.env["DB_DATABASE"];
 | 
			
		||||
 | 
			
		||||
if (!host) throw new ReferenceError("Environment variable DB_HOST is missing");
 | 
			
		||||
if (!username) throw new ReferenceError("Environment variable DB_USERNAME is missing");
 | 
			
		||||
if (!password) throw new ReferenceError("Environment variable DB_PASSWORD is missing");
 | 
			
		||||
if (!database) throw new ReferenceError("Environment variable DB_DATABASE is missing");
 | 
			
		||||
 | 
			
		||||
const dataSource = new DataSource({
 | 
			
		||||
    type: "postgres",
 | 
			
		||||
    host: host,
 | 
			
		||||
    port: 5432,
 | 
			
		||||
    username: username,
 | 
			
		||||
    password: password,
 | 
			
		||||
    database: database,
 | 
			
		||||
    migrationsRun: true,
 | 
			
		||||
    migrations: [ path.join(__dirname, "/migrations/*") ],
 | 
			
		||||
    entities: [Badword,GuildSetting],
 | 
			
		||||
    migrationsTransactionMode: "each"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default dataSource;
 | 
			
		||||
							
								
								
									
										9
									
								
								src/data/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/data/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
import dataSource from "./dataSource";
 | 
			
		||||
import { Badword, GuildSetting } from "./model";
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    dataSource as database,
 | 
			
		||||
    Badword,
 | 
			
		||||
    GuildSetting
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/data/migrations/1669251793386-data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/data/migrations/1669251793386-data.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class data1669251793386 implements MigrationInterface {
 | 
			
		||||
    name = 'data1669251793386'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
            CREATE TABLE "badword" (
 | 
			
		||||
                "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
 | 
			
		||||
                "guildID" character varying,
 | 
			
		||||
                "value" character varying NOT NULL,
 | 
			
		||||
                CONSTRAINT "PK_b5034b5fcec4ccac0c288e37f3a" PRIMARY KEY ("id")
 | 
			
		||||
            )
 | 
			
		||||
        `);
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
            CREATE TABLE "guild_setting" (
 | 
			
		||||
                "id" character varying NOT NULL,
 | 
			
		||||
                "notificationChannelID" character varying,
 | 
			
		||||
                "isPremiumUntil" date,
 | 
			
		||||
                CONSTRAINT "PK_56f0d706a92e999b4e967abae5f" PRIMARY KEY ("id")
 | 
			
		||||
            )
 | 
			
		||||
        `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
            DROP TABLE "guild_setting"
 | 
			
		||||
        `);
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
            DROP TABLE "badword"
 | 
			
		||||
        `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/data/model/badword.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/data/model/badword.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
 | 
			
		||||
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Badword {
 | 
			
		||||
    @PrimaryGeneratedColumn("uuid")
 | 
			
		||||
    id!: string;
 | 
			
		||||
 | 
			
		||||
    @Column("varchar", { nullable: true })
 | 
			
		||||
    guildID!: string | null;
 | 
			
		||||
 | 
			
		||||
    @Column("varchar")
 | 
			
		||||
    value!: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/data/model/guildSetting.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/data/model/guildSetting.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { Entity, PrimaryColumn, Column } from "typeorm";
 | 
			
		||||
 | 
			
		||||
@Entity()
 | 
			
		||||
export class GuildSetting {
 | 
			
		||||
    @PrimaryColumn("varchar")
 | 
			
		||||
    id!: string;
 | 
			
		||||
 | 
			
		||||
    @Column("varchar", { nullable: true, default: null })
 | 
			
		||||
    notificationChannelID!: string | null;
 | 
			
		||||
 | 
			
		||||
    @Column("date", { nullable: true, default: null })
 | 
			
		||||
    isPremiumUntil!: Date | null;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/data/model/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/data/model/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { Badword } from "./badword";
 | 
			
		||||
import { GuildSetting } from "./guildSetting";
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    Badword,
 | 
			
		||||
    GuildSetting
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +1,25 @@
 | 
			
		||||
/* eslint-disable prefer-rest-params */
 | 
			
		||||
/**
 | 
			
		||||
 * This module swaps the default console outputs to own functions and adds a timestamp to each message
 | 
			
		||||
 * (c) 2022 AstroGD
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 import path from "path";
 | 
			
		||||
 import fs from "fs-extra";
 | 
			
		||||
 
 | 
			
		||||
 export default function() {
 | 
			
		||||
     fs.ensureDirSync(path.join(__dirname, "../data/log/"));
 | 
			
		||||
     fs.ensureDirSync(path.join(__dirname, "../../data/logs/"));
 | 
			
		||||
 
 | 
			
		||||
     const callDate = new Date();
 | 
			
		||||
     const logFileName = `${callDate.getUTCFullYear()}-${`0${callDate.getUTCMonth() + 1}`.slice(-2)}-${`0${callDate.getUTCDate()}`.slice(-2)}-${`0${callDate.getUTCHours()}`.slice(-2)}-${`0${callDate.getUTCMinutes()}`.slice(-2)}-${`0${callDate.getUTCSeconds()}`.slice(-2)}-${`00${callDate.getUTCMilliseconds()}`.slice(-3)}.log`;
 | 
			
		||||
     const logFile = fs.createWriteStream(path.join(__dirname, `../data/log/${logFileName}`));
 | 
			
		||||
     const logFile = fs.createWriteStream(path.join(__dirname, `../../data/logs/${logFileName}`));
 | 
			
		||||
     const out = new console.Console(process.stdout, process.stderr, true);
 | 
			
		||||
     const logConsole = new console.Console(logFile);
 | 
			
		||||
 
 | 
			
		||||
     function log() {
 | 
			
		||||
         const now = new Date();
 | 
			
		||||
         const Prepend = `[i] [${now.getUTCFullYear()}-${`0${now.getUTCMonth() + 1}`.slice(-2)}-${`0${now.getUTCDate()}`.slice(-2)} ${`0${now.getUTCHours()}`.slice(-2)}:${`0${now.getUTCMinutes()}`.slice(-2)}:${`0${now.getUTCSeconds()}`.slice(-2)}.${`00${now.getUTCMilliseconds()}`.slice(-3)}] `;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0].toString().normalize()}`;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0]?.toString().normalize()}`;
 | 
			
		||||
 
 | 
			
		||||
         for (let i = 0; i < arguments.length; i++) {
 | 
			
		||||
             arguments[i] = arguments[i].split("\n").join(`\n${new Array(31).join(" ")}`);
 | 
			
		||||
@@ -31,10 +32,10 @@
 | 
			
		||||
     function warn() {
 | 
			
		||||
         const now = new Date();
 | 
			
		||||
         const Prepend = `[W] [${now.getUTCFullYear()}-${`0${now.getUTCMonth() + 1}`.slice(-2)}-${`0${now.getUTCDate()}`.slice(-2)} ${`0${now.getUTCHours()}`.slice(-2)}:${`0${now.getUTCMinutes()}`.slice(-2)}:${`0${now.getUTCSeconds()}`.slice(-2)}.${`00${now.getUTCMilliseconds()}`.slice(-3)}] `;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0].toString().normalize()}`;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0]?.toString().normalize()}`;
 | 
			
		||||
 
 | 
			
		||||
         for (let i = 0; i < arguments.length; i++) {
 | 
			
		||||
             const argument = arguments[i].toString().normalize();
 | 
			
		||||
             const argument = arguments[i]?.toString().normalize();
 | 
			
		||||
             arguments[i] = argument.split("\n").join(`\n${new Array(31).join(" ")}`);
 | 
			
		||||
         }
 | 
			
		||||
 
 | 
			
		||||
@@ -45,10 +46,10 @@
 | 
			
		||||
     function error() {
 | 
			
		||||
         const now = new Date();
 | 
			
		||||
         const Prepend = `==== [ERROR] [${now.getUTCFullYear()}-${`0${now.getUTCMonth() + 1}`.slice(-2)}-${`0${now.getUTCDate()}`.slice(-2)} ${`0${now.getUTCHours()}`.slice(-2)}:${`0${now.getUTCMinutes()}`.slice(-2)}:${`0${now.getUTCSeconds()}`.slice(-2)}.${`00${now.getUTCMilliseconds()}`.slice(-3)}] ====\n`;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0].toString().normalize()}`;
 | 
			
		||||
         arguments[0] = `${Prepend}${arguments[0]?.toString().normalize()}`;
 | 
			
		||||
 
 | 
			
		||||
         for (let i = 0; i < arguments.length; i++) {
 | 
			
		||||
             arguments[i] = arguments[i].toString().normalize();
 | 
			
		||||
             arguments[i] = arguments[i]?.toString().normalize();
 | 
			
		||||
         }
 | 
			
		||||
 
 | 
			
		||||
         out.error(...arguments);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    /* Visit https://aka.ms/tsconfig to read more about this file */
 | 
			
		||||
 | 
			
		||||
    /* Projects */
 | 
			
		||||
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
 | 
			
		||||
    "incremental": true,                                 /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
 | 
			
		||||
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
 | 
			
		||||
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
 | 
			
		||||
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
 | 
			
		||||
@@ -47,21 +47,21 @@
 | 
			
		||||
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
 | 
			
		||||
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 | 
			
		||||
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
 | 
			
		||||
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
 | 
			
		||||
    "sourceMap": true,                                   /* Create source map files for emitted JavaScript files. */
 | 
			
		||||
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
 | 
			
		||||
    "outDir": "./build",                                 /* Specify an output folder for all emitted files. */
 | 
			
		||||
    // "removeComments": true,                           /* Disable emitting comments. */
 | 
			
		||||
    "removeComments": true,                              /* Disable emitting comments. */
 | 
			
		||||
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
 | 
			
		||||
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 | 
			
		||||
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
 | 
			
		||||
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 | 
			
		||||
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 | 
			
		||||
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 | 
			
		||||
    "inlineSourceMap": true,                             /* Include sourcemap files inside the emitted JavaScript. */
 | 
			
		||||
    "inlineSources": true,                               /* Include source code in the sourcemaps inside the emitted JavaScript. */
 | 
			
		||||
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 | 
			
		||||
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
 | 
			
		||||
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 | 
			
		||||
    "emitBOM": true,                                     /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 | 
			
		||||
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
 | 
			
		||||
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 | 
			
		||||
    "stripInternal": true,                               /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 | 
			
		||||
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 | 
			
		||||
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 | 
			
		||||
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 | 
			
		||||
@@ -99,5 +99,9 @@
 | 
			
		||||
    /* Completeness */
 | 
			
		||||
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
 | 
			
		||||
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "index.ts",
 | 
			
		||||
    "src"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user