Compare commits

..

No commits in common. "main" and "v3.1.1" have entirely different histories.
main ... v3.1.1

73 changed files with 2567 additions and 3014 deletions

View file

@ -7,7 +7,7 @@
# any secret values.
BOT_TOKEN=
BOT_VER=3.2.4
BOT_VER=3.1.1
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060
@ -15,14 +15,10 @@ BOT_CLIENTID=682942374040961060
ABOUT_FUNDING=https://ko-fi.com/vylpes
ABOUT_REPO=https://gitea.vylpes.xyz/RabbitLabs/vylbot-app
CACHE_INTERVAL=1800000 # 30 minutes
DB_HOST=127.0.0.1
DB_PORT=3101
DB_NAME=vylbot
DB_AUTH_USER=dev
DB_AUTH_PASS=dev
DB_SYNC=true
DB_LOGGING=true
DB_DATA_LOCATION=./.temp/database
DB_ROOT_HOST=0.0.0.0
DB_LOGGING=true

View file

@ -7,7 +7,7 @@ steps:
- name: deploy
image: appleboy/drone-ssh
settings:
host: 192.168.1.115
host: 192.168.68.121
username: vylpes
password:
from_secret: ssh_password
@ -28,7 +28,7 @@ steps:
- name: stage
image: appleboy/drone-ssh
settings:
host: 192.168.1.115
host: 192.168.68.121
username: vylpes
password:
from_secret: ssh_password
@ -54,11 +54,11 @@ steps:
- yarn install --frozen-lockfile
- yarn build
# - name: test
# image: node
# commands:
# - yarn install --frozen-lockfile
# - yarn test
- name: test
image: node
commands:
- yarn install --frozen-lockfile
- yarn test
trigger:
branch:

View file

@ -1,67 +0,0 @@
name: Deploy To Production
on:
push:
branches:
- main
jobs:
build:
environment: prod
runs-on: node
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 18.x
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test
- name: "Copy files over to location"
run: rsync -rvzP . ${{ secrets.PROD_REPO_PATH }}
deploy:
environment: prod
needs: build
runs-on: node
steps:
- uses: https://github.com/appleboy/ssh-action@v1.0.0
env:
DB_NAME: ${{ secrets.PROD_DB_NAME }}
DB_AUTH_USER: ${{ secrets.PROD_DB_AUTH_USER }}
DB_AUTH_PASS: ${{ secrets.PROD_DB_AUTH_PASS }}
DB_HOST: ${{ secrets.PROD_DB_HOST }}
DB_PORT: ${{ secrets.PROD_DB_PORT }}
DB_ROOT_HOST: ${{ secrets.PROD_DB_ROOT_HOST }}
DB_SYNC: ${{ secrets.PROD_DB_SYNC }}
DB_LOGGING: ${{ secrets.PROD_DB_LOGGING }}
DB_DATA_LOCATION: ${{ secrets.PROD_DB_DATA_LOCATION }}
SERVER_PATH: ${{ secrets.PROD_SSH_SERVER_PATH }}
BOT_TOKEN: ${{ secrets.PROD_BOT_TOKEN }}
BOT_VER: ${{ vars.PROD_BOT_VER }}
BOT_AUTHOR: ${{ vars.PROD_BOT_AUTHOR }}
BOT_OWNERID: ${{ vars.PROD_BOT_OWNERID }}
BOT_CLIENTID: ${{ vars.PROD_BOT_CLIENTID }}
ABOUT_FUNDING: ${{ vars.PROD_ABOUT_FUNDING }}
ABOUT_REPO: ${{ vars.PROD_ABOUT_REPO }}
CACHE_INTERVAL: ${{ vars.PROD_CACHE_INTERVAL }}
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
port: ${{ secrets.PROD_SSH_PORT }}
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,CACHE_INTERVAL
script: |
source .sshrc \
&& cd /home/vylpes/apps/vylbot/vylbot_prod \
&& docker compose down \
&& (pm2 stop vylbot_prod || true) \
&& (pm2 delete vylbot_prod || true) \
&& docker compose up -d \
&& sleep 10 \
&& yarn db:up \
&& pm2 start --name vylbot_prod dist/vylbot.js

View file

@ -1,69 +0,0 @@
name: Deploy To Stage
on:
push:
branches:
- develop
- hotfix/*
- release/*
jobs:
build:
environment: prod
runs-on: node
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 18.x
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test
- name: "Copy files over to location"
run: rsync -rvzP --delete . ${{ secrets.STAGE_REPO_PATH }}
deploy:
environment: prod
needs: build
runs-on: node
steps:
- uses: https://github.com/appleboy/ssh-action@v1.0.0
env:
DB_NAME: ${{ secrets.STAGE_DB_NAME }}
DB_AUTH_USER: ${{ secrets.STAGE_DB_AUTH_USER }}
DB_AUTH_PASS: ${{ secrets.STAGE_DB_AUTH_PASS }}
DB_HOST: ${{ secrets.STAGE_DB_HOST }}
DB_PORT: ${{ secrets.STAGE_DB_PORT }}
DB_ROOT_HOST: ${{ secrets.STAGE_DB_ROOT_HOST }}
DB_SYNC: ${{ secrets.STAGE_DB_SYNC }}
DB_LOGGING: ${{ secrets.STAGE_DB_LOGGING }}
DB_DATA_LOCATION: ${{ secrets.STAGE_DB_DATA_LOCATION }}
SERVER_PATH: ${{ secrets.STAGE_SSH_SERVER_PATH }}
BOT_TOKEN: ${{ secrets.STAGE_BOT_TOKEN }}
BOT_VER: ${{ github.ref_name }}
BOT_AUTHOR: ${{ vars.STAGE_BOT_AUTHOR }}
BOT_OWNERID: ${{ vars.STAGE_BOT_OWNERID }}
BOT_CLIENTID: ${{ vars.STAGE_BOT_CLIENTID }}
ABOUT_FUNDING: ${{ vars.STAGE_ABOUT_FUNDING }}
ABOUT_REPO: ${{ vars.STAGE_ABOUT_REPO }}
CACHE_INTERVAL: ${{ vars.STAGE_CACHE_INTERVAL }}
with:
host: ${{ secrets.STAGE_SSH_HOST }}
username: ${{ secrets.STAGE_SSH_USER }}
key: ${{ secrets.STAGE_SSH_KEY }}
port: ${{ secrets.STAGE_SSH_PORT }}
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,CACHE_INTERVAL
script: |
source .sshrc \
&& cd /home/vylpes/apps/vylbot/vylbot_stage \
&& docker compose down \
&& (pm2 stop vylbot_stage || true) \
&& (pm2 delete vylbot_stage || true) \
&& docker compose up -d \
&& sleep 10 \
&& yarn db:up \
&& pm2 start --name vylbot_stage dist/vylbot.js

View file

@ -1,23 +0,0 @@
name: Test
on:
push:
branches:
- feature/*
- renovate/*
jobs:
build:
environment: stage
runs-on: node
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 18.x
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test

View file

@ -24,6 +24,6 @@ Please describe the tests that you ran to verify the changes. Provide instructio
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that provide my fix is effective or that my feature works
- [ ] I have added tests that provde my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules

3
.gitignore vendored
View file

@ -105,5 +105,4 @@ dist
config.json
.DS_Store
ormconfig.json
.temp/
ormconfig.json

24
.prod.env Normal file
View file

@ -0,0 +1,24 @@
# Security Warning! Do not commit this file to any VCS!
# This is a local file to speed up development process,
# so you don't have to change your environment variables.
#
# This is not applied to `.env.template`!
# Template files must be committed to the VCS, but must not contain
# any secret values.
BOT_TOKEN=
BOT_VER=3.1.1
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=680083120896081954
ABOUT_FUNDING=https://ko-fi.com/vylpes
ABOUT_REPO=https://gitea.vylpes.xyz/RabbitLabs/vylbot-app
DB_HOST=127.0.0.1
DB_PORT=3121
DB_NAME=vylbot
DB_AUTH_USER=prod
DB_AUTH_PASS=prod
DB_SYNC=false
DB_LOGGING=false

24
.stage.env Normal file
View file

@ -0,0 +1,24 @@
# Security Warning! Do not commit this file to any VCS!
# This is a local file to speed up development process,
# so you don't have to change your environment variables.
#
# This is not applied to `.env.template`!
# Template files must be committed to the VCS, but must not contain
# any secret values.
BOT_TOKEN=
BOT_VER=3.1.1
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=1016767908740857949
ABOUT_FUNDING=https://ko-fi.com/vylpes
ABOUT_REPO=https://gitea.vylpes.xyz/RabbitLabs/vylbot-app
DB_HOST=127.0.0.1
DB_PORT=3111
DB_NAME=vylbot
DB_AUTH_USER=stage
DB_AUTH_PASS=stage
DB_SYNC=false
DB_LOGGING=false

View file

@ -26,11 +26,11 @@ event.message.delete.channel: Sets the channel the bot will log message delete e
event.message.update.enabled: Enables/Disables the message delete log event (Default: "false")
event.message.update.channel: Sets the channel the bot will log message delete events to (Default: "message-logs")
event.member.add.enabled: Enables/Disables the member join log event (Default: "false")
event.member.add.channel: Sets the channel the bot will log member join events to (Default: "member-logs")
event.member.add.enabled: Enables/Disables the message delete log event (Default: "false")
event.member.add.channel: Sets the channel the bot will log message delete events to (Default: "member-logs")
event.member.remove.enabled: Enables/Disables the member leave log event (Default: "false")
event.member.remove.channel: Sets the channel the bot will log member leave events to (Default: "member-logs")
event.member.remove.enabled: Enables/Disables the message delete log event (Default: "false")
event.member.remove.channel: Sets the channel the bot will log message delete events to (Default: "member-logs")
event.member.update.enabled: Enables/Disables the member update log event (Default: "false")
event.member.update.channel: Sets the channel the bot will log member update events to (Default: "member-logs")
event.member.update.enabled: Enables/Disables the message delete log event (Default: "false")
event.member.update.channel: Sets the channel the bot will log message delete events to (Default: "member-logs")

View file

@ -1,2 +0,0 @@
ALTER TABLE server
ADD LastCached datetime NOT NULL DEFAULT '2024-03-01 18:10:04';

View file

@ -1 +0,0 @@
DROP TABLE auto_kick_config;

View file

@ -1,10 +0,0 @@
CREATE TABLE auto_kick_config (
Id varchar(255) NOT NULL,
WhenCreated datetime NOT NULL,
WhenUpdated datetime NOT NULL,
ServerId varchar(255) NOT NULL,
RoleId varchar(255) NOT NULL,
KickTime int NOT NULL,
NoticeTime int NULL,
NoticeChannelId varchar(255) NULL
);

View file

@ -1,2 +0,0 @@
ALTER TABLE auto_kick_config
ADD PRIMARY KEY (Id);

31
docker-compose.prod.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.9"
volumes:
prod_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=vylbot
- MYSQL_USER=prod
- MYSQL_PASSWORD=prod
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "3121:3306"
volumes:
- prod_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3122:80"
environment:
- PMA_ARBITRARY=1

31
docker-compose.stage.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.9"
volumes:
stage_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=vylbot
- MYSQL_USER=stage
- MYSQL_PASSWORD=stage
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "3111:3306"
volumes:
- stage_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3112:80"
environment:
- PMA_ARBITRARY=1

View file

@ -1,17 +1,31 @@
version: "3.9"
volumes:
dev_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=$DB_NAME
- MYSQL_USER=$DB_AUTH_USER
- MYSQL_PASSWORD=$DB_AUTH_PASS
- MYSQL_ROOT_PASSWORD=$DB_AUTH_PASS
- MYSQL_ROOT_HOST=$DB_ROOT_HOST
- MYSQL_DATABASE=vylbot
- MYSQL_USER=dev
- MYSQL_PASSWORD=dev
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "$DB_PORT:3306"
- "3101:3306"
volumes:
- $DB_DATA_LOCATION:/var/lib/mysql
- dev_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3102:80"
environment:
- PMA_ARBITRARY=1

View file

@ -1,6 +1,6 @@
{
"name": "vylbot-app",
"version": "3.2.4",
"version": "3.1.1",
"description": "A discord bot made for Vylpes' Den",
"main": "./dist/vylbot",
"typings": "./dist",
@ -8,10 +8,9 @@
"clean": "rm -rf node_modules/ dist/",
"build": "tsc",
"start": "node ./dist/vylbot",
"test": "echo true",
"test": "jest",
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
"db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js",
"db:create": "typeorm migration:create ./src/database/migrations",
"release": "np --no-publish"
},
"repository": {
@ -27,28 +26,26 @@
"homepage": "https://github.com/Vylpes/vylbot-app",
"funding": "https://ko-fi.com/vylpes",
"dependencies": {
"@discordjs/rest": "^2.0.0",
"@discordjs/rest": "^1.1.0",
"@types/jest": "^29.0.0",
"@types/uuid": "^9.0.0",
"cron": "^3.3.1",
"discord.js": "^14.3.0",
"dotenv": "^16.0.0",
"emoji-regex": "^10.0.0",
"jest": "^29.0.0",
"jest-mock-extended": "^3.0.0",
"minimatch": "9.0.3",
"minimatch": "9.0.2",
"mysql": "^2.18.1",
"random-bunny": "^2.1.6",
"random-bunny": "^2.0.5",
"ts-jest": "^29.0.0",
"typeorm": "0.3.20"
"typeorm": "0.3.17"
},
"resolutions": {
"**/semver": "^7.5.2",
"**/undici": "^6.0.0"
"**/semver": "^7.5.2"
},
"devDependencies": {
"@types/node": "^20.0.0",
"np": "^10.0.0",
"np": "^8.0.4",
"typescript": "^5.0.0"
}
}

View file

@ -1,42 +0,0 @@
import { ButtonInteraction, CacheType } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent";
import SettingsHelper from "../helpers/SettingsHelper";
export default class Verify extends ButtonEvent {
public override async execute(interaction: ButtonInteraction<CacheType>) {
if (!interaction.guildId || !interaction.guild) return;
const roleName = await SettingsHelper.GetSetting("verification.role", interaction.guildId);
if (!roleName) return;
const role = interaction.guild.roles.cache.find(x => x.name == roleName);
if (!role) {
await interaction.reply({
content: `Unable to find the role, ${roleName}`,
ephemeral: true,
});
return;
}
const member = interaction.guild.members.cache.find(x => x.id == interaction.user.id);
if (!member || !member.manageable) {
await interaction.reply({
content: "Unable to give role to user",
ephemeral: true,
});
return;
}
await member.roles.add(role);
await interaction.reply({
content: "Given role",
ephemeral: true,
});
}
}

View file

@ -1,5 +1,6 @@
import { Client, Partials } from "discord.js";
import { Client } from "discord.js";
import * as dotenv from "dotenv";
import { createConnection } from "typeorm";
import { EventType } from "../constants/EventType";
import ICommandItem from "../contracts/ICommandItem";
import IEventItem from "../contracts/IEventItem";
@ -8,21 +9,13 @@ import { Command } from "../type/command";
import { Events } from "./events";
import { Util } from "./util";
import AppDataSource from "../database/dataSources/appDataSource";
import ButtonEventItem from "../contracts/ButtonEventItem";
import { ButtonEvent } from "../type/buttonEvent";
import CacheHelper from "../helpers/CacheHelper";
import TimerHelper from "../helpers/TimerHelper";
import AutoKick from "../timers/AutoKick";
export class CoreClient extends Client {
private static _commandItems: ICommandItem[];
private static _eventItems: IEventItem[];
private static _buttonEvents: ButtonEventItem[];
private static _baseClient: Client;
private _events: Events;
private _util: Util;
private _timerHelper: TimerHelper;
public static get commandItems(): ICommandItem[] {
return this._commandItems;
@ -32,25 +25,15 @@ export class CoreClient extends Client {
return this._eventItems;
}
public static get buttonEvents(): ButtonEventItem[] {
return this._buttonEvents;
}
public static get baseClient(): Client {
return this._baseClient;
}
constructor(intents: number[], partials: Partials[]) {
super({ intents: intents, partials: partials });
constructor(intents: number[]) {
super({ intents: intents });
dotenv.config();
CoreClient._commandItems = [];
CoreClient._eventItems = [];
CoreClient._buttonEvents = [];
this._events = new Events();
this._util = new Util();
this._timerHelper = new TimerHelper();
}
public async start() {
@ -59,16 +42,8 @@ export class CoreClient extends Client {
return;
}
CoreClient._baseClient = this;
await AppDataSource.initialize()
.then(() => {
console.log("Data Source Initialized");
this._timerHelper.AddTimer("0 * * * *", "Europe/London", AutoKick, false);
this._timerHelper.StartAllTimers();
})
.then(() => console.log("Data Source Initialized"))
.catch((err) => console.error("Error Initialising Data Source", err));
super.on("interactionCreate", this._events.onInteractionCreate);
@ -76,10 +51,6 @@ export class CoreClient extends Client {
await super.login(process.env.BOT_TOKEN);
this.guilds.cache.forEach(async (guild) => {
await CacheHelper.UpdateServerCache(guild);
});
this._util.loadEvents(this, CoreClient._eventItems);
this._util.loadSlashCommands(this);
}
@ -102,13 +73,4 @@ export class CoreClient extends Client {
CoreClient._eventItems.push(item);
}
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent) {
const item: ButtonEventItem = {
ButtonId: buttonId,
Event: event,
};
CoreClient._buttonEvents.push(item);
}
}

View file

@ -1,18 +1,40 @@
import { Interaction } from "discord.js";
import ChatInputCommand from "./interactionCreate/chatInputCommand";
import Button from "./interactionCreate/button";
import ICommandItem from "../contracts/ICommandItem";
import SettingsHelper from "../helpers/SettingsHelper";
import { CoreClient } from "./client";
export class Events {
public async onInteractionCreate(interaction: Interaction) {
if (!interaction.isChatInputCommand()) return;
if (!interaction.guildId) return;
if (interaction.isChatInputCommand()) {
ChatInputCommand.onChatInput(interaction);
const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", interaction.guildId);
const disabledCommands = disabledCommandsString?.split(",");
const disabledCommandsMessage = await SettingsHelper.GetSetting("commands.disabled.message", interaction.guildId);
if (disabledCommands?.find(x => x == interaction.commandName)) {
await interaction.reply(disabledCommandsMessage || "This command is disabled.");
return;
}
if (interaction.isButton()) {
Button.onButtonClicked(interaction);
const item = CoreClient.commandItems.find(x => x.Name == interaction.commandName && !x.ServerId);
const itemForServer = CoreClient.commandItems.find(x => x.Name == interaction.commandName && x.ServerId == interaction.guildId);
let itemToUse: ICommandItem;
if (!itemForServer) {
if (!item) {
await interaction.reply('Command not found');
return;
}
itemToUse = item;
} else {
itemToUse = itemForServer;
}
itemToUse.Command.execute(interaction);
}
// Emit when bot is logged in and ready to use

View file

@ -1,17 +0,0 @@
import { ButtonInteraction } from "discord.js";
import { CoreClient } from "../client";
export default class Button {
public static async onButtonClicked(interaction: ButtonInteraction) {
if (!interaction.isButton) return;
const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(" ")[0]);
if (!item) {
await interaction.reply("Event not found.");
return;
}
item.Event.execute(interaction);
}
}

View file

@ -1,27 +0,0 @@
import { Interaction } from "discord.js";
import { CoreClient } from "../client";
import ICommandItem from "../../contracts/ICommandItem";
export default class ChatInputCommand {
public static async onChatInput(interaction: Interaction) {
if (!interaction.isChatInputCommand()) return;
const item = CoreClient.commandItems.find(x => x.Name == interaction.commandName && !x.ServerId);
const itemForServer = CoreClient.commandItems.find(x => x.Name == interaction.commandName && x.ServerId == interaction.guildId);
let itemToUse: ICommandItem;
if (!itemForServer) {
if (!item) {
await interaction.reply("Command not found.");
return;
}
itemToUse = item;
} else {
itemToUse = itemForServer;
}
itemToUse.Command.execute(interaction);
}
}

View file

@ -37,7 +37,7 @@ export class Util {
.flatMap(x => x.Command.CommandBuilder);
if (!client.guilds.cache.has(guild)) continue;
rest.put(
Routes.applicationGuildCommands(process.env.BOT_CLIENTID!, guild),
{

View file

@ -6,7 +6,7 @@ export default class AddRole extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('addlobby')
.setDescription('Add lobby channel')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)

View file

@ -7,7 +7,7 @@ export default class ListLobby extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('listlobby')
.setDescription('Lists all channels set up as lobbies')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers);

View file

@ -6,7 +6,7 @@ export default class Lobby extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('lobby')
.setDescription('Attempt to organise a lobby');
}

View file

@ -7,7 +7,7 @@ export default class RemoveLobby extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('removelobby')
.setDescription('Remove a lobby channel')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)

View file

@ -1,4 +1,4 @@
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import EmbedColours from "../../constants/EmbedColours";
import SettingsHelper from "../../helpers/SettingsHelper";
import { Command } from "../../type/command";
@ -7,7 +7,7 @@ export default class Entry extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('entry')
.setDescription('Sends the entry embed')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers);
@ -24,8 +24,6 @@ export default class Entry extends Command {
.setTitle("Welcome")
.setDescription(`Welcome to the server! Please make sure to read the rules in the <#${rulesChannelId}> channel and type the code found there in here to proceed to the main part of the server.`);
const channel = interaction.channel as TextChannel;
await channel.send({ embeds: [ embed ]});
await interaction.channel.send({ embeds: [ embed ]});
}
}

View file

@ -7,7 +7,7 @@ export default class ConfigRole extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('configrole')
.setDescription('Toggle your roles')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ManageRoles)

View file

@ -7,7 +7,7 @@ export default class Role extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('role')
.setDescription('Toggle your roles')
.addSubcommand(subcommand =>

View file

@ -6,7 +6,7 @@ export default class About extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('about')
.setDescription('About VylBot');
}

View file

@ -9,7 +9,7 @@ export default class Audits extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("audits")
.setDescription("View audits of a particular user in the server")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -93,7 +93,7 @@ export default class Audits extends Command {
private async SendAuditForUser(interaction: CommandInteraction) {
if (!interaction.guildId) return;
const user = interaction.options.get('target')?.user;
const user = interaction.options.getUser('target');
if (!user) {
await interaction.reply("User not found.");
@ -191,7 +191,7 @@ export default class Audits extends Command {
private async AddAudit(interaction: CommandInteraction) {
if (!interaction.guildId) return;
const user = interaction.options.get('target')?.user;
const user = interaction.options.getUser('target');
const auditType = interaction.options.get('type');
const reasonInput = interaction.options.get('reason');

View file

@ -1,113 +0,0 @@
import {ChatInputCommandInteraction, CommandInteraction, EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder} from "discord.js";
import {Command} from "../type/command";
import TimeLengthInput from "../helpers/TimeLengthInput";
import AutoKickHelper from "../helpers/AutoKickHelper";
export default class Autokick extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName("autokick")
.setDescription("Configure the auto kick functionality")
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addSubcommand(x => x
.setName("set")
.setDescription("Set the configuration")
.addRoleOption(y => y
.setName("role")
.setDescription("The role the user needs to be auto kicked")
.setRequired(true))
.addStringOption(y => y
.setName("kicktime")
.setDescription("The time with the role before being kicked (Ex: 2h 30m)")
.setRequired(true))
.addStringOption(y => y
.setName("noticetime")
.setDescription("The time before being kicked when a notification is sent (Ex: 2h 30m)"))
.addChannelOption(y => y
.setName("noticechannel")
.setDescription("The channel to send the notification to")))
.addSubcommand(x => x
.setName("unset")
.setDescription("Unset the current configuration"));
}
public override async execute(interaction: CommandInteraction) {
if (!interaction.isChatInputCommand()) return;
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case "set":
await this.set(interaction);
break;
case "unset":
await this.unset(interaction);
break;
}
}
private async set(interaction: ChatInputCommandInteraction) {
if (!interaction.guildId) return;
const roleOption = interaction.options.getRole("role", true);
const kickTimeOption = interaction.options.getString("kicktime", true);
const noticeTimeOption = interaction.options.getString("noticetime");
const noticeChannelOption = interaction.options.getChannel("noticechannel");
const roleId = roleOption.id;
const kickTimeInput = new TimeLengthInput(kickTimeOption);
const noticeTimeInput = noticeTimeOption ? new TimeLengthInput(noticeTimeOption) : undefined;
const noticeChannelId = noticeChannelOption?.id;
if ((noticeTimeInput && !noticeTimeOption) || (!noticeTimeInput && noticeChannelOption)) {
await interaction.reply("Both `noticetime` and `noticechannel` must be set if you want a notification embed");
return;
}
await AutoKickHelper.SetSetting(interaction.guildId, roleId, kickTimeInput.GetMilliseconds(), noticeTimeInput?.GetMilliseconds(), noticeChannelId);
const embed = new EmbedBuilder()
.setTitle("Auto Kick")
.setDescription("Configured auto kick for this server")
.addFields([
{
name: "Role",
value: roleOption.name,
inline: true,
},
{
name: "Kick Time",
value: kickTimeInput.GetLengthShort(),
inline: true,
},
]);
if (noticeTimeInput) {
embed.addFields([
{
name: "Notice Time",
value: noticeTimeInput.GetLengthShort(),
},
{
name: "Notice Channel",
value: noticeChannelOption!.name!,
inline: true,
},
]);
}
await interaction.reply({
embeds: [ embed ],
});
}
private async unset(interaction: ChatInputCommandInteraction) {
if (!interaction.guildId) return;
await AutoKickHelper.UnsetSetting(interaction.guildId);
await interaction.reply("Unset the auto kick configuration for this server");
}
}

View file

@ -9,7 +9,7 @@ export default class Ban extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("ban")
.setDescription("Ban a member from the server with an optional reason")
.setDefaultMemberPermissions(PermissionsBitField.Flags.BanMembers)
@ -44,7 +44,6 @@ export default class Ban extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Banned")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",

View file

@ -7,7 +7,7 @@ export default class Bunny extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("bunny")
.setDescription("Get a random picture of a rabbit.");
}
@ -15,8 +15,6 @@ export default class Bunny extends Command {
public override async execute(interaction: CommandInteraction) {
if (!interaction.isChatInputCommand()) return;
await interaction.deferReply();
const subreddits = [
'rabbits',
'bunnieswithhats',
@ -39,9 +37,9 @@ export default class Bunny extends Command {
.setURL(`https://reddit.com${result.Result!.Permalink}`)
.setFooter({ text: `r/${selectedSubreddit} · ${result.Result!.Ups} upvotes`});
await interaction.editReply({ embeds: [ embed ]});
await interaction.reply({ embeds: [ embed ]});
} else {
await interaction.editReply("There was an error running this command.");
await interaction.reply("There was an error running this command.");
}
}
}

View file

@ -5,7 +5,7 @@ export default class Clear extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("clear")
.setDescription("Clears the channel of messages")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ManageMessages)

View file

@ -1,4 +1,4 @@
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import SettingsHelper from "../helpers/SettingsHelper";
import StringTools from "../helpers/StringTools";
import { Command } from "../type/command";
@ -7,7 +7,7 @@ export default class Code extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('code')
.setDescription('Manage the verification code of the server')
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -59,8 +59,6 @@ export default class Code extends Command {
.setTitle("Entry Code")
.setDescription(code);
const channel = interaction.channel as TextChannel;
await channel.send({ embeds: [ embed ]});
await interaction.channel.send({ embeds: [ embed ]});
}
}

View file

@ -10,7 +10,7 @@ export default class Config extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('config')
.setDescription('Configure the current server')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
@ -116,15 +116,9 @@ export default class Config extends Command {
const setting = server.Settings.filter(x => x.Key == key.value)[0];
if (setting) {
await interaction.reply(`\`${key.value}\`: \`${setting.Value}\``);
await interaction.reply(`\`${key}\`: \`${setting.Value}\``);
} else {
var defaultValue = DefaultValues.GetValue(key.value.toString());
if (defaultValue) {
await interaction.reply(`\`${key.value}\`: \`${defaultValue}\` <DEFAULT>`);
} else {
await interaction.reply(`\`${key.value}\`: <NONE>`);
}
await interaction.reply(`\`${key}\`: \`${DefaultValues.GetValue(key.value.toString())}\` <DEFAULT>`);
}
}
@ -197,4 +191,4 @@ export default class Config extends Command {
await interaction.reply('Setting has been set.');
}
}
}

View file

@ -6,7 +6,7 @@ export default class Disable extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('disable')
.setDescription('Disables a command')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)

View file

@ -6,7 +6,7 @@ export default class Ignore extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('ignore')
.setDescription('Ignore events in this channel')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);

View file

@ -9,7 +9,7 @@ export default class Kick extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("kick")
.setDescription("Kick a member from the server with an optional reason")
.setDefaultMemberPermissions(PermissionsBitField.Flags.KickMembers)
@ -44,7 +44,6 @@ export default class Kick extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Kicked")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",

View file

@ -1,30 +0,0 @@
import { CommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command";
import SettingsHelper from "../helpers/SettingsHelper";
export default class Linkonly extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName("linkonly")
.setDescription("Set the link only channel, leave blank to disable")
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addChannelOption(x => x
.setName("channel")
.setDescription("The channel"));
}
public override async execute(interaction: CommandInteraction) {
if (!interaction.guild) return;
const channel = interaction.options.get("channel")?.channel;
const channelid = channel?.id ?? "";
const channelName = channel?.name ?? "<NONE>";
await SettingsHelper.SetSetting("channel.linkonly", interaction.guild.id, channelid);
await interaction.reply(`Set the link only channel to \`${channelName}\``);
}
}

View file

@ -9,7 +9,7 @@ export default class Mute extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("mute")
.setDescription("(DEPRECATED) Mute a member in the server with an optional reason")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -42,7 +42,6 @@ export default class Mute extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Muted")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",
@ -54,14 +53,7 @@ export default class Mute extends Command {
},
]);
const mutedRoleName = await SettingsHelper.GetSetting('role.muted', interaction.guildId);
if (!mutedRoleName) {
await interaction.reply('Unable to find configuration. Please contact the bot author.');
return;
}
const mutedRole = interaction.guild.roles.cache.find(role => role.name == mutedRoleName);
const mutedRole = interaction.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
if (!mutedRole) {
await interaction.reply('Muted role not found.');

View file

@ -7,7 +7,7 @@ export default class Poll extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('poll')
.setDescription('Run a poll, automatically adding reaction emojis as options')
.addStringOption(option =>

View file

@ -1,8 +1,7 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder, TextChannel } from "discord.js";
import { CommandInteraction, EmbedBuilder, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import { existsSync, readFileSync } from "fs";
import EmbedColours from "../constants/EmbedColours";
import { Command } from "../type/command";
import SettingsHelper from "../helpers/SettingsHelper";
interface IRules {
title?: string;
@ -15,36 +14,13 @@ export default class Rules extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName('rules')
.setDescription("Rules-related commands")
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
.addSubcommand(x =>
x
.setName('embeds')
.setDescription('Send the rules embeds for this server'))
.addSubcommand(x =>
x
.setName('access')
.setDescription('Send the server verification embed button'));
super.CommandBuilder = new SlashCommandBuilder()
.setName("rules")
.setDescription("Send the rules embeds for this server")
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
}
public override async execute(interaction: CommandInteraction) {
if (!interaction.isChatInputCommand()) return;
switch (interaction.options.getSubcommand()) {
case "embeds":
await this.SendEmbeds(interaction);
break;
case "access":
await this.SendAccessButton(interaction);
break;
default:
await interaction.reply("Subcommand doesn't exist.");
}
}
private async SendEmbeds(interaction: CommandInteraction) {
if (!interaction.guildId) return;
if (!existsSync(`${process.cwd()}/data/rules/${interaction.guildId}.json`)) {
@ -79,7 +55,7 @@ export default class Rules extends Command {
embeds.push(embed);
});
const channel = interaction.channel as TextChannel;
const channel = interaction.channel;
if (!channel) {
await interaction.reply({ content: "Channel not found.", ephemeral: true });
@ -95,29 +71,4 @@ export default class Rules extends Command {
await interaction.reply({ embeds: [ successEmbed ], ephemeral: true });
}
private async SendAccessButton(interaction: CommandInteraction) {
if (!interaction.guildId) return;
const buttonLabel = await SettingsHelper.GetSetting("rules.access.label", interaction.guildId);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents([
new ButtonBuilder()
.setCustomId('verify')
.setStyle(ButtonStyle.Primary)
.setLabel(buttonLabel || "Verify")
]);
const channel = interaction.channel as TextChannel;
await channel.send({
components: [ row ]
});
await interaction.reply({
content: "Success",
ephemeral: true,
});
}
}

View file

@ -6,7 +6,7 @@ export default class Setup extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName('setup')
.setDescription('Makes the server ready to be configured')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);

View file

@ -10,7 +10,7 @@ export default class Timeout extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("timeout")
.setDescription("Timeouts a user out, sending them a DM with the reason if possible")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -64,7 +64,6 @@ export default class Timeout extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Timed Out")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",

View file

@ -7,7 +7,7 @@ export default class Unmute extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("unmute")
.setDescription("(DEPRECATED) Unmute a member in the server with an optional reason")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -40,7 +40,6 @@ export default class Unmute extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Unmuted")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",
@ -52,14 +51,7 @@ export default class Unmute extends Command {
},
]);
const mutedRoleName = await SettingsHelper.GetSetting('role.muted', interaction.guildId);
if (!mutedRoleName) {
await interaction.reply('Unable to find configuration. Please contact the bot author.');
return;
}
const mutedRole = interaction.guild.roles.cache.find(role => role.name == mutedRoleName);
const mutedRole = interaction.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
if (!mutedRole) {
await interaction.reply('Muted role not found.');

View file

@ -9,7 +9,7 @@ export default class Warn extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
super.CommandBuilder = new SlashCommandBuilder()
.setName("warn")
.setDescription("Warns a member in the server with an optional reason")
.setDefaultMemberPermissions(PermissionsBitField.Flags.ModerateMembers)
@ -41,7 +41,6 @@ export default class Warn extends Command {
.setColor(EmbedColours.Ok)
.setTitle("Member Warned")
.setDescription(`<@${targetUser.user.id}> \`${targetUser.user.tag}\``)
.setThumbnail(targetUser.user.avatarURL())
.addFields([
{
name: "Moderator",

View file

@ -6,7 +6,7 @@ export default class DefaultValues {
this.SetValues();
const res = this.values.find(x => x.Key == key);
if (!res) {
return undefined;
}
@ -31,7 +31,6 @@ export default class DefaultValues {
// Rules (Command)
this.values.push({ Key: "rules.file", Value: "data/rules/rules" });
this.values.push({ Key: "rules.access.label", Value: "Verify" });
// Channels
this.values.push({ Key: "channels.logs.message", Value: "message-logs" });
@ -44,24 +43,21 @@ export default class DefaultValues {
this.values.push({ Key: "verification.role", Value: "Entry" });
this.values.push({ Key: "verification.code", Value: "" });
// Gif Only Mode
this.values.push({ Key: "channel.linkonly", Value: "" })
// Event
this.values.push({ Key: "event.message.delete.enabled", Value: "false" });
this.values.push({ Key: "event.message.delete.channel", Value: "message-logs" });
this.values.push({ Key: "event.message.update.enabled", Value: "false" });
this.values.push({ Key: "event.message.update.channel", Value: "message-logs" });
this.values.push({ Key: "event.member.add.enabled", Value: "false" });
this.values.push({ Key: "event.member.add.channel", Value: "member-logs" });
this.values.push({ Key: "event.member.remove.enabled", Value: "false" });
this.values.push({ Key: "event.member.remove.channel", Value: "member-logs" });
this.values.push({ Key: "event.member.update.enabled", Value: "false" });
this.values.push({ Key: "event.member.update.channel", Value: "member-logs" });
this.values.push({ Key: "event.member.remove.channel", Value: "member-logs" });
}
}

View file

@ -1,5 +1,3 @@
export default class EmbedColours {
public static readonly Ok = 0x3050ba;
public static readonly Warning = 0xffbf00;
public static readonly Danger = 0xd2042d;
}

View file

@ -1,8 +0,0 @@
import { ButtonEvent } from "../type/buttonEvent";
interface ButtonEventItem {
ButtonId: string,
Event: ButtonEvent,
}
export default ButtonEventItem;

View file

@ -1,61 +0,0 @@
import {Column, Entity} from "typeorm";
import AppDataSource from "../dataSources/appDataSource";
import BaseEntity from "../../contracts/BaseEntity";
@Entity()
export default class AutoKickConfig extends BaseEntity {
constructor(serverId: string, roleId: string, kickTime: number, noticeTime?: number, noticeChannelId?: string) {
super();
this.ServerId = serverId;
this.RoleId = roleId;
this.KickTime = kickTime;
this.NoticeTime = noticeTime;
this.NoticeChannelId = noticeChannelId;
}
@Column()
ServerId: string;
@Column()
RoleId: string;
@Column({ type: "int" })
KickTime: number;
@Column({ type: "int", nullable: true })
NoticeTime?: number;
@Column({ nullable: true })
NoticeChannelId?: string;
public UpdateBasicDetails(roleId: string, kickTime: number, noticeTime?: number, noticeChannelId?: string) {
this.RoleId = roleId;
this.KickTime = kickTime;
this.NoticeTime = noticeTime;
this.NoticeChannelId = noticeChannelId;
}
public static async FetchOneByServerIdAndRoleId(serverId: string, roleId: string): Promise<AutoKickConfig | null> {
const repository = AppDataSource.getRepository(AutoKickConfig);
const query = repository
.createQueryBuilder("config")
.where("config.serverId = :serverId", { serverId })
.andWhere("config.roleId = :roleId", { roleId })
.getOne();
return query;
}
public static async FetchAllByServerId(serverId: string): Promise<AutoKickConfig[]> {
const repository = AppDataSource.getRepository(AutoKickConfig);
const query = repository
.createQueryBuilder("config")
.where("config.serverId = :serverId", { serverId })
.getMany();
return query;
}
}

View file

@ -1,4 +1,4 @@
import { Column, Entity, OneToMany } from "typeorm";
import { Entity, OneToMany } from "typeorm";
import BaseEntity from "../../contracts/BaseEntity";
import Role from "./Role";
import Setting from "./Setting";
@ -9,22 +9,14 @@ export default class Server extends BaseEntity {
super();
this.Id = serverId;
this.LastCached = new Date();
}
@Column({ default: "2024-03-01 18:10:04" })
LastCached: Date;
@OneToMany(() => Setting, x => x.Server)
Settings: Setting[];
@OneToMany(() => Role, x => x.Server)
Roles: Role[];
public UpdateLastCached(lastCached: Date) {
this.LastCached = lastCached;
}
public AddSettingToServer(setting: Setting) {
this.Settings.push(setting);
}

View file

@ -1,15 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm"
import MigrationHelper from "../../../helpers/MigrationHelper"
export class AddServerCacheDate1709316734401 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up('1709316734401-AddServerCacheDate', '3.2.1', [
"01-Server",
], queryRunner);
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View file

@ -1,19 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import MigrationHelper from "../../../helpers/MigrationHelper";
export class CreateAutoKickConfig1732973911304 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up("1732973911304-createAutoKickConfig", "3.2.4", [
"01-AutoKickConfig-Table",
"02-AutoKickConfig-Key",
], queryRunner)
}
public async down(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Down("1732973911304-createAutoKickConfig", "3.2.4", [
"01-AutoKickConfig",
], queryRunner)
}
}

View file

@ -1,13 +1,10 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import EmbedColours from "../../constants/EmbedColours";
import SettingsHelper from "../../helpers/SettingsHelper";
import CacheHelper from "../../helpers/CacheHelper";
export default async function GuildMemberAdd(member: GuildMember) {
if (!member.guild) return;
await CacheHelper.UpdateServerCache(member.guild);
const enabled = await SettingsHelper.GetSetting("event.member.add.enabled", member.guild.id);
if (!enabled || enabled.toLowerCase() != "true") return;

View file

@ -1,13 +1,10 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import EmbedColours from "../../constants/EmbedColours";
import SettingsHelper from "../../helpers/SettingsHelper";
import CacheHelper from "../../helpers/CacheHelper";
export default async function GuildMemberRemove(member: GuildMember) {
if (!member.guild) return;
await CacheHelper.UpdateServerCache(member.guild);
const enabled = await SettingsHelper.GetSetting("event.member.remove.enabled", member.guild.id);
if (!enabled || enabled.toLowerCase() != "true") return;

View file

@ -1,13 +1,8 @@
import { GuildMember } from "discord.js";
import NicknameChanged from "./GuildMemberUpdate/NicknameChanged";
import CacheHelper from "../../helpers/CacheHelper";
export default async function GuildMemberUpdate(oldMember: GuildMember, newMember: GuildMember) {
const updatedFromCache = await CacheHelper.UpdateServerCache(newMember.guild);
if (updatedFromCache) return;
if (oldMember.nickname !== newMember.nickname) { // Nickname change
if (oldMember.nickname != newMember.nickname) { // Nickname change
await NicknameChanged(oldMember, newMember);
}
}

View file

@ -1,17 +1,11 @@
import { Message } from "discord.js";
import SettingsHelper from "../../helpers/SettingsHelper";
import VerificationCheck from "./MessageCreate/VerificationCheck";
import CacheHelper from "../../helpers/CacheHelper";
import LinkOnlyMode from "./MessageCreate/LinkOnlyMode";
export default async function MessageCreate(message: Message) {
if (!message.guild) return;
if (message.author.bot) return;
await CacheHelper.UpdateServerCache(message.guild);
await LinkOnlyMode(message);
const isVerificationEnabled = await SettingsHelper.GetSetting("verification.enabled", message.guild.id);
if (isVerificationEnabled && isVerificationEnabled.toLocaleLowerCase() == "true") {

View file

@ -1,16 +0,0 @@
import { Message } from "discord.js";
import SettingsHelper from "../../../helpers/SettingsHelper";
export default async function LinkOnlyMode(message: Message) {
if (!message.guild) return;
const gifOnlyMode = await SettingsHelper.GetSetting("channel.linkonly", message.guild.id);
if (gifOnlyMode != message.channel.id) return;
if (message.content.startsWith("https://") || message.content.startsWith("http://")) return;
if (!message.deletable) return;
await message.delete();
}

View file

@ -2,14 +2,11 @@ import { EmbedBuilder, Message, TextChannel } from "discord.js";
import EmbedColours from "../../constants/EmbedColours";
import IgnoredChannel from "../../database/entities/IgnoredChannel";
import SettingsHelper from "../../helpers/SettingsHelper";
import CacheHelper from "../../helpers/CacheHelper";
export default async function MessageDelete(message: Message) {
if (!message.guild) return;
if (message.author.bot) return;
await CacheHelper.UpdateServerCache(message.guild);
const enabled = await SettingsHelper.GetSetting("event.message.delete.enabled", message.guild.id);
if (!enabled || enabled.toLowerCase() != "true") return;
@ -36,8 +33,8 @@ export default async function MessageDelete(message: Message) {
if (message.attachments.size > 0) {
embed.addFields([
{
name: `Attachments (${message.attachments.size})`,
value: `\`\`\`${message.attachments.map(x => x.name).join("\n")}\`\`\``
name: "Attachments",
value: `\`\`\`${message.attachments.map(x => x.url).join("\n")}\`\`\``
}
]);
}

View file

@ -2,14 +2,10 @@ import { EmbedBuilder, Message, TextChannel } from "discord.js";
import EmbedColours from "../../constants/EmbedColours";
import IgnoredChannel from "../../database/entities/IgnoredChannel";
import SettingsHelper from "../../helpers/SettingsHelper";
import CacheHelper from "../../helpers/CacheHelper";
export default async function MessageUpdate(oldMessage: Message, newMessage: Message) {
if (!newMessage.guild) return;
if (newMessage.author.bot) return;
await CacheHelper.UpdateServerCache(newMessage.guild);
if (oldMessage.content == newMessage.content) return;
const enabled = await SettingsHelper.GetSetting("event.message.update.enabled", newMessage.guild.id);

View file

@ -1,37 +0,0 @@
import AutoKickConfig from "../database/entities/AutoKickConfig";
export default class AutoKickHelper {
public static async GetSetting(serverId: string): Promise<AutoKickConfig | null> {
const configs = await AutoKickConfig.FetchAllByServerId(serverId);
if (configs.length != 1) {
return null;
}
return configs[0];
}
public static async SetSetting(serverId: string, roleId: string, kickTime: number, noticeTime?: number, noticeChannelId?: string) {
const configs = await AutoKickConfig.FetchAllByServerId(serverId);
if (configs.length == 0) {
const config = new AutoKickConfig(serverId, roleId, kickTime, noticeTime, noticeChannelId);
await config.Save(AutoKickConfig, config);
return;
}
const config = configs[0];
config.UpdateBasicDetails(roleId, kickTime, noticeTime, noticeChannelId);
await config.Save(AutoKickConfig, config);
}
public static async UnsetSetting(serverId: string) {
const configs = await AutoKickConfig.FetchAllByServerId(serverId);
for (let config of configs) {
await AutoKickConfig.Remove(AutoKickConfig, config);
}
}
}

View file

@ -1,33 +0,0 @@
import { Guild } from "discord.js";
import Server from "../database/entities/Server";
export default class CacheHelper {
public static async UpdateServerCache(guild: Guild): Promise<boolean> {
const cacheInterval = process.env.CACHE_INTERVAL;
if (!cacheInterval) return false;
let server = await Server.FetchOneById(Server, guild.id);
if (!server) {
server = new Server(guild.id);
await server.Save(Server, server);
await CacheHelper.UpdateCache(guild);
return true;
} else if (server.LastCached.getTime() + Number(cacheInterval) > Date.now()) {
await CacheHelper.UpdateCache(guild);
return true;
}
return false;
}
private static async UpdateCache(guild: Guild) {
console.log(`Updating cache for ${guild.name} (${guild.id})`);
await guild.members.fetch();
}
}

View file

@ -1,81 +0,0 @@
import {CronJob} from "cron";
import {primitive} from "../type/primitive";
import {v4} from "uuid";
interface Timer {
id: string;
job: CronJob;
context: Map<string, primitive>;
onTick: ((context: Map<string, primitive>) => void) | ((context: Map<string, primitive>) => Promise<void>);
runOnStart: boolean;
}
export default class TimerHelper {
private _timers: Timer[];
constructor() {
this._timers = [];
}
public AddTimer(
cronTime: string,
timeZone: string,
onTick: ((context: Map<string, primitive>) => void) | ((context: Map<string, primitive>) => Promise<void>),
runOnStart: boolean = false): string {
const context = new Map<string, primitive>();
const job = new CronJob(
cronTime,
() => {
onTick(context);
},
null,
false,
timeZone,
);
const id = v4();
this._timers.push({
id,
job,
context,
onTick,
runOnStart,
});
return id;
}
public StartAllTimers() {
this._timers.forEach(timer => this.StartJob(timer));
}
public StopAllTimers() {
this._timers.forEach(timer => timer.job.stop());
}
public StartTimer(id: string) {
const timer = this._timers.find(x => x.id == id);
if (!timer) return;
this.StartJob(timer);
}
public StopTimer(id: string) {
const timer = this._timers.find(x => x.id == id);
if (!timer) return;
timer.job.stop();
}
private StartJob(timer: Timer) {
timer.job.start();
if (timer.runOnStart) {
timer.onTick(timer.context);
}
}
}

View file

@ -4,7 +4,6 @@ import { EventType } from "./constants/EventType";
// Command Imports
import About from "./commands/about";
import Audits from "./commands/audits";
import Autokick from "./commands/autokick";
import Ban from "./commands/ban";
import Bunny from "./commands/bunny";
import Clear from "./commands/clear";
@ -13,7 +12,6 @@ import Config from "./commands/config";
import Disable from "./commands/disable";
import Ignore from "./commands/ignore";
import Kick from "./commands/kick";
import Linkonly from "./commands/linkonly";
import Mute from "./commands/mute";
import Poll from "./commands/poll";
import Role from "./commands/Role/role";
@ -39,14 +37,10 @@ import MessageDelete from "./events/MessageEvents/MessageDelete";
import MessageUpdate from "./events/MessageEvents/MessageUpdate";
import MessageCreate from "./events/MessageEvents/MessageCreate";
// Button Event Imports
import Verify from "./buttonEvents/verify";
export default class Registry {
public static RegisterCommands() {
CoreClient.RegisterCommand("about", new About());
CoreClient.RegisterCommand("audits", new Audits());
CoreClient.RegisterCommand("autokick", new Autokick());
CoreClient.RegisterCommand("ban", new Ban());
CoreClient.RegisterCommand("bunny", new Bunny());
CoreClient.RegisterCommand("clear", new Clear());
@ -55,7 +49,6 @@ export default class Registry {
CoreClient.RegisterCommand("disable", new Disable());
CoreClient.RegisterCommand("ignore", new Ignore());
CoreClient.RegisterCommand("kick", new Kick());
CoreClient.RegisterCommand("linkonly", new Linkonly());
CoreClient.RegisterCommand("mute", new Mute());
CoreClient.RegisterCommand("poll", new Poll());
CoreClient.RegisterCommand("rules", new Rules());
@ -91,8 +84,4 @@ export default class Registry {
CoreClient.RegisterEvent(EventType.MessageUpdate, MessageUpdate);
CoreClient.RegisterEvent(EventType.MessageCreate, MessageCreate);
}
public static RegisterButtonEvents() {
CoreClient.RegisterButtonEvent("verify", new Verify());
}
}

View file

@ -1,114 +0,0 @@
import { EmbedBuilder } from "discord.js";
import {CoreClient} from "../client/client";
import AutoKickConfig from "../database/entities/AutoKickConfig";
import EmbedColours from "../constants/EmbedColours";
export default async function AutoKick() {
const client = CoreClient.baseClient;
const autoKickConfigs = await AutoKickConfig.FetchAll(AutoKickConfig);
for (let config of autoKickConfigs) {
const guild = client.guilds.cache.find(x => x.id == config.ServerId) || await client.guilds.fetch(config.ServerId);
if (!guild) {
console.error("Guild not found");
continue;
}
await guild.members.fetch();
const role = guild.roles.cache.find(x => x.id == config.RoleId);
if (!role) {
console.error("Role not found in guild");
continue;
}
for (let memberEntity of role.members) {
const member = memberEntity[1];
if (!member.kickable) {
console.error("Member not kickable");
continue;
}
const whenToKick = new Date(member.joinedTimestamp! + config.KickTime);
const now = new Date();
if (whenToKick < now) {
await member.kick("Auto Kicked");
if (config.NoticeChannelId) {
const channel = guild.channels.cache.find(x => x.id == config.NoticeChannelId) || await guild.channels.fetch(config.NoticeChannelId);
if (!channel?.isSendable()) {
console.log("Channel not sendable");
continue;
}
const embed = new EmbedBuilder()
.setTitle("Auto Kicked User")
.setColor(EmbedColours.Danger)
.setThumbnail(member.user.avatarURL())
.addFields([
{
name: "User",
value: `<@${member.user.id}> \`${member.user.username}\``,
inline: true,
},
]);
await channel.send({
embeds: [ embed ],
});
}
} else if (config.NoticeChannelId && config.NoticeTime) {
const whenToNotice = new Date(whenToKick.getTime() - config.NoticeTime);
whenToNotice.setMinutes(0, 0, 0);
whenToNotice.setHours(whenToNotice.getHours() + 1);
const channel = guild.channels.cache.find(x => x.id == config.NoticeChannelId) || await guild.channels.fetch(config.NoticeChannelId);
if (!channel?.isSendable()) {
console.error("Channel not sendable");
continue;
}
if (now.getMonth() == whenToNotice.getMonth()
&& now.getDate() == whenToNotice.getDate()
&& now.getHours() == whenToNotice.getHours()) {
const nextHour = new Date(whenToKick);
nextHour.setMinutes(0, 0, 0);
nextHour.setHours(whenToKick.getHours() + 1);
const embed = new EmbedBuilder()
.setTitle("Auto Kick Notice")
.setColor(EmbedColours.Warning)
.setThumbnail(member.user.avatarURL())
.addFields([
{
name: "User",
value: `<@${member.user.id}> \`${member.user.username}\``,
inline: true,
},
{
name: "When To Kick",
value: `<t:${Math.round(nextHour.getTime() / 1000)}:R>`,
inline: true,
},
]);
await channel.send({
embeds: [ embed ],
});
}
}
}
}
}

View file

@ -1,5 +0,0 @@
import { ButtonInteraction } from "discord.js";
export abstract class ButtonEvent {
abstract execute(interaction: ButtonInteraction): Promise<void>;
}

View file

@ -1 +0,0 @@
export type primitive = string | number | boolean;

View file

@ -1,7 +1,7 @@
import { CoreClient } from "./client/client";
import * as dotenv from "dotenv";
import registry from "./registry";
import { IntentsBitField, Partials } from "discord.js";
import { IntentsBitField } from "discord.js";
dotenv.config();
@ -30,13 +30,9 @@ const client = new CoreClient([
IntentsBitField.Flags.GuildMessages,
IntentsBitField.Flags.GuildMembers,
IntentsBitField.Flags.MessageContent,
], [
Partials.GuildMember,
Partials.User,
]);
registry.RegisterCommands();
registry.RegisterEvents();
registry.RegisterButtonEvents();
client.start();

4268
yarn.lock

File diff suppressed because it is too large Load diff