Private chat

- added commands to the private chat
- added Guild Utility commands
pull/1/head
Trivernis 5 years ago
parent 8843b40e8c
commit 6c84aefd96

@ -1 +1,3 @@
2b-bot-2 # 2B Bot 2
A typescript rewrite of my discordbot 2b.

@ -1,12 +1,18 @@
import {Client, Guild} from "discord.js"; import {Client, Guild} from "discord.js";
import { Config } from "./lib/utils/Config"; import {Config} from "./lib/utils/Config";
import { DefaultConfig } from "./lib/utils/DefaultConfig"; import {DefaultConfig} from "./lib/utils/DefaultConfig";
import * as path from "path"; import * as path from "path";
import * as fsx from "fs-extra"; import * as fsx from "fs-extra";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import {BotLogger} from "./lib/utils/BotLogger"; import {BotLogger} from "./lib/utils/BotLogger";
import {DataHandler} from "./lib/DataHandler"; import {DataHandler} from "./lib/DataHandler";
import {GuildHandler} from "./lib/GuildHandler"; import {GuildHandler} from "./lib/GuildHandler";
import {CommandCollection} from "./lib/CommandCollection";
import {Command} from "./lib/Command";
import {globalCommands} from "./commands/global";
import {privateCommands} from "./commands/private";
import {parseMessage} from "./lib/utils";
import {CommandPermission} from "./lib/CommandPermission";
const configFile = "config.yaml"; const configFile = "config.yaml";
@ -18,6 +24,7 @@ export class Bot {
public readonly config: Config; public readonly config: Config;
public readonly logger: BotLogger; public readonly logger: BotLogger;
public readonly dataHandler: DataHandler; public readonly dataHandler: DataHandler;
private commandCollection: CommandCollection<Command>;
private guildHandlers: any = {}; private guildHandlers: any = {};
/** /**
@ -28,6 +35,9 @@ export class Bot {
this.logger = new BotLogger(this.config.logging.directory, this.config.logging.level); this.logger = new BotLogger(this.config.logging.directory, this.config.logging.level);
this.client = new Client(); this.client = new Client();
this.dataHandler = new DataHandler(this); this.dataHandler = new DataHandler(this);
this.commandCollection = new CommandCollection<Command>();
this.commandCollection.include(globalCommands);
this.commandCollection.include(privateCommands);
} }
/** /**
@ -69,6 +79,16 @@ export class Bot {
if (message.guild) { if (message.guild) {
const handler = await this.getGuildHandler(message.guild); const handler = await this.getGuildHandler(message.guild);
await handler.onMessage(message); await handler.onMessage(message);
} else {
this.logger.debug(`<PM: ${message.author.tag}> ${message.content}`);
const userPermission = this.config.owners.includes(message.author.tag) ?
CommandPermission.OWNER : CommandPermission.REGULAR;
const CommandClass = parseMessage(message, this.commandCollection,
this.config.prefix, userPermission);
if (CommandClass) {
const command = new CommandClass(this);
command.invoke(message);
}
} }
} }
}); });

@ -1,4 +1,6 @@
import {CommandCollection} from "../../lib/CommandCollection"; import {CommandCollection} from "../../lib/CommandCollection";
import {Ping} from "./utility/Ping";
export const globalCommands = new CommandCollection([ export const globalCommands = new CommandCollection([
Ping,
]); ]);

@ -0,0 +1,17 @@
import {Command} from "../../../lib/Command";
import {CommandPermission} from "../../../lib/CommandPermission";
import {Message} from "discord.js";
export class Ping extends Command {
public static commandName = "ping";
public static permission = CommandPermission.REGULAR;
public static description = "Replies with the bots ping.";
/**
* Replies with the current ping.
* @param msg
*/
public invoke(msg: Message): void {
msg.channel.send(`My latency is **${Math.round(this.bot.client.ping)}** ms.`);
}
}

@ -1,6 +1,10 @@
import {CommandCollection} from "../../lib/CommandCollection"; import {CommandCollection} from "../../lib/CommandCollection";
import {AddAdminRoles} from "./utility/AddAdminRoles"; import {AddAdminRoles} from "./utility/AddAdminRoles";
import {SetPrefix} from "./utility/SetPrefix";
import {RemoveAdminRoles} from "./utility/RemoveAdminRoles";
export const guildCommands = new CommandCollection([ export const guildCommands = new CommandCollection([
AddAdminRoles, AddAdminRoles,
SetPrefix,
RemoveAdminRoles,
]); ]);

@ -12,12 +12,15 @@ export class AddAdminRoles extends GuildCommand {
*/ */
public async invoke(msg: Message) { public async invoke(msg: Message) {
const args = AddAdminRoles.getArgs(msg.content); const args = AddAdminRoles.getArgs(msg.content);
this.bot.logger.debug(args[0]);
if (args.length < 2) { if (args.length < 2) {
msg.channel.send("No argument for role names provided."); msg.channel.send("No argument for role names provided.");
} else { } else {
const roles = args.splice(1); const roles = args.splice(1);
this.guildHandler.settings.adminRoles.push(...roles); for (const role of roles) {
if (this.guildHandler.settings.adminRoles.includes(role)) {
this.guildHandler.settings.adminRoles.push(role);
}
}
msg.channel.send(`Added **${roles.join("**, **")}** to the admin roles.`); msg.channel.send(`Added **${roles.join("**, **")}** to the admin roles.`);
} }
} }

@ -0,0 +1,24 @@
import {CommandPermission} from "../../../lib/CommandPermission";
import {GuildCommand} from "../../../lib/GuildCommand";
import {Message} from "discord.js";
export class RemoveAdminRoles extends GuildCommand {
public static commandName = "removeAdminRoles";
public static description = "Removes one or more roles from the configured roles";
public static permission = CommandPermission.ADMIN;
/**
* Removes all specified roles from the admin roles.
*/
public invoke(msg: Message): Promise<void> | void {
const args = RemoveAdminRoles.getArgs(msg.content);
if (args.length < 2) {
msg.channel.send("No argument for role names provided.");
} else {
const roles = args.splice(1);
const adminRoles = this.guildHandler.settings.adminRoles;
this.guildHandler.settings.adminRoles = adminRoles.filter((role) => !roles.includes(role));
msg.channel.send(`Removed **${roles.join("**, **")}** from the admin roles.`);
}
}
}

@ -0,0 +1,27 @@
import {GuildCommand} from "../../../lib/GuildCommand";
import {CommandPermission} from "../../../lib/CommandPermission";
import {Message} from "discord.js";
export class SetPrefix extends GuildCommand {
public static commandName = "setPrefix";
public static permission = CommandPermission.ADMIN;
/**
* Sets the prefix for a guild.
* @param msg
*/
public invoke(msg: Message): Promise<void> | void {
const args = SetPrefix.getArgs(msg.content);
if (args.length > 1) {
const prefix: string = args[1];
if (/^\S+$/.test(prefix)) {
this.guildHandler.settings.prefix = prefix;
msg.channel.send(`Changed the command prefix to **${prefix}**`);
} else {
msg.channel.send(`**${prefix}** Is not a valid prefix. \nA prefix must be a non-whitespace sequence of characters.`);
}
} else {
msg.channel.send("You need to provide a prefix as commadn argument.");
}
}
}

@ -11,6 +11,11 @@ export abstract class Command {
*/ */
public static commandName: string; public static commandName: string;
/**
* The description of the command
*/
public static description: string;
/** /**
* The time to live in seconds before the instance is deleted. * The time to live in seconds before the instance is deleted.
*/ */
@ -31,17 +36,17 @@ export abstract class Command {
this.createdAt = Date.now(); this.createdAt = Date.now();
} }
public invoke?(msg: Message): Promise<void>; public invoke?(msg: Message): Promise<void>|void;
/** /**
* A function that is executed when the answer to the command was sent. * A function that is executed when the answer to the command was sent.
*/ */
public onSent?(answer: Message): Promise<void>; public onSent?(answer: Message): Promise<void>|void;
/** /**
* A function that is executed when a reaction is added to the command answer * A function that is executed when a reaction is added to the command answer
*/ */
public onReaction?(reaction: MessageReaction): Promise<void>; public onReaction?(reaction: MessageReaction): Promise<void>|void;
/** /**
* returns the name of the command to make it accessible. * returns the name of the command to make it accessible.

@ -9,13 +9,14 @@ import {globalCommands} from "../commands/global";
import {Command} from "./Command"; import {Command} from "./Command";
import {BotLogger} from "./utils/BotLogger"; import {BotLogger} from "./utils/BotLogger";
import {ProxyEventEmitter} from "./utils/ProxyEventEmitter"; import {ProxyEventEmitter} from "./utils/ProxyEventEmitter";
import {parseMessage} from "./utils";
/** /**
* Handles all guild related tasks. * Handles all guild related tasks.
*/ */
export class GuildHandler { export class GuildHandler {
private guild: Guild; private guild: Guild;
private bot: Bot; private readonly bot: Bot;
private guildData: GuildData; private guildData: GuildData;
private guildSettings: GuildSettings; private guildSettings: GuildSettings;
private guildSettingsProxy: ProxyEventEmitter; private guildSettingsProxy: ProxyEventEmitter;
@ -61,26 +62,14 @@ export class GuildHandler {
* @param message * @param message
*/ */
public async onMessage(message: Message): Promise<void> { public async onMessage(message: Message): Promise<void> {
const commandPattern = new RegExp( `\\s*${this.guildSettings.prefix}(\\w+)`);
this.logger.debug(`Command Pattern is: ${commandPattern}`);
this.logger.debug(`<${this.guild.name}:${message.author.tag}>"${message.content}"`); this.logger.debug(`<${this.guild.name}:${message.author.tag}>"${message.content}"`);
if (commandPattern.test(message.content)) { const CommandClass = parseMessage(message, this.commandCollection,
this.logger.debug("Message matches command syntax."); this.settings.prefix, this.getMembersHighestRole(message.member));
const commandString = commandPattern.exec(message.content)[1];
const CommandClass = this.commandCollection.findByName(commandString);
if (CommandClass) { if (CommandClass) {
this.logger.debug(`${commandString} -> ${CommandClass.name}`);
this.logger.debug(`Member Permission: ${this.getMembersHighestRole(message.member)}, command permission: ${CommandClass.permission}`);
if (CommandClass.permission <= this.getMembersHighestRole(message.member)) {
const command = new CommandClass(this.bot, this); const command = new CommandClass(this.bot, this);
await command.invoke(message); await command.invoke(message);
} else {
message.channel.send("You don't have permission for that command.");
}
}
} }
} }
@ -99,9 +88,9 @@ export class GuildHandler {
private getMembersHighestRole(guildMember: GuildMember) { private getMembersHighestRole(guildMember: GuildMember) {
const adminRoles = this.guildSettings.adminRoles; const adminRoles = this.guildSettings.adminRoles;
const djRoles = this.guildSettings.djRoles; const djRoles = this.guildSettings.djRoles;
if (adminRoles.find((role) => GuildHandler.memberHasRole(guildMember, role)).length > 0) { if (adminRoles.find((role) => GuildHandler.memberHasRole(guildMember, role))) {
return CommandPermission.ADMIN; return CommandPermission.ADMIN;
} else if (djRoles.find((role) => GuildHandler.memberHasRole(guildMember, role)).length > 0) { } else if (djRoles.find((role) => GuildHandler.memberHasRole(guildMember, role))) {
return CommandPermission.DJ; return CommandPermission.DJ;
} else { } else {
return CommandPermission.REGULAR; return CommandPermission.REGULAR;
@ -114,6 +103,6 @@ export class GuildHandler {
* @param roleName * @param roleName
*/ */
private static memberHasRole(guildMember: GuildMember, roleName: string) { private static memberHasRole(guildMember: GuildMember, roleName: string) {
return guildMember.roles.filter((role, key) => role.name === roleName).size > 0; return guildMember.roles.filter((role) => role.name === roleName).size > 0;
} }
} }

@ -1,5 +1,15 @@
import {Config} from "./utils/Config";
export class GuildSettings { export class GuildSettings {
/**
* Constructor with config that assigns some config specific stuff.
* @param config
*/
constructor(config: Config) {
this.prefix = config.prefix;
}
/** /**
* The prefix of the bot. * The prefix of the bot.
*/ */

@ -1,6 +1,7 @@
import {Table, Column, Model, NotNull} from "sequelize-typescript"; import {Table, Column, Model, NotNull} from "sequelize-typescript";
import {JSON as SQJSON} from "sequelize"; import {JSON as SQJSON} from "sequelize";
import {GuildSettings} from "../GuildSettings"; import {GuildSettings} from "../GuildSettings";
import {DefaultConfig} from "../utils/DefaultConfig";
@Table({underscored: true}) @Table({underscored: true})
export class Guild extends Model<Guild> { export class Guild extends Model<Guild> {
@ -10,6 +11,7 @@ export class Guild extends Model<Guild> {
public guildId: string; public guildId: string;
@NotNull @NotNull
@Column({allowNull: false, type: SQJSON, defaultValue: new GuildSettings()}) // @ts-ignore
@Column({allowNull: false, type: SQJSON, defaultValue: new GuildSettings(new DefaultConfig())})
public settings: GuildSettings; public settings: GuildSettings;
} }

@ -6,11 +6,6 @@ export abstract class Config {
*/ */
public presenceDuration: number = 300000; public presenceDuration: number = 300000;
/**
* The maximum number of commands in a sequence.
*/
public maxCommandSequenceLength: number = 10;
/** /**
* The number of commands that are allowed in a minute by one user. * The number of commands that are allowed in a minute by one user.
*/ */
@ -38,4 +33,9 @@ export abstract class Config {
* The owners of the bot that have elevated privileges * The owners of the bot that have elevated privileges
*/ */
public owners: string[]; public owners: string[];
/**
* The prefix of the bot.
*/
public prefix: string;
} }

@ -6,11 +6,6 @@ export class DefaultConfig extends Config {
*/ */
public presenceDuration: number = 300000; public presenceDuration: number = 300000;
/**
* The maximum number of commands in a sequence.
*/
public maxCommandSequenceLength: number = 10;
/** /**
* The number of commands a user is allowed to execute in one minute. * The number of commands a user is allowed to execute in one minute.
*/ */
@ -36,4 +31,9 @@ export class DefaultConfig extends Config {
* The owners of the bot that have elevated privileges * The owners of the bot that have elevated privileges
*/ */
public owners: string[] = []; public owners: string[] = [];
/**
* The prefix of the bot.
*/
public prefix: string = "~";
} }

@ -1,6 +1,8 @@
import {EventEmitter} from "events"; import {EventEmitter} from "events";
import {type} from "os";
/**
* An event emitter that can be used to listen to events regarding object properties.
*/
export class ProxyEventEmitter extends EventEmitter implements ProxyHandler<any> { export class ProxyEventEmitter extends EventEmitter implements ProxyHandler<any> {
/** /**

@ -0,0 +1,31 @@
import {Message} from "discord.js";
import {CommandPermission} from "../CommandPermission";
import {CommandCollection} from "../CommandCollection";
import {Command} from "../Command";
/**
* Parses a message and returns the corresponding command.
* @param message
* @param commandCollection
* @param prefix
* @param userPermission
*/
export function parseMessage(message: Message, commandCollection: CommandCollection<any>,
prefix: string, userPermission: CommandPermission = 0): any {
const commandPattern = new RegExp( `\\s*${prefix}(\\w+)`);
if (commandPattern.test(message.content)) {
const commandString = commandPattern.exec(message.content)[1];
const CommandClass = commandCollection.findByName(commandString);
if (CommandClass) {
if (CommandClass.permission <= userPermission) {
return CommandClass;
} else {
message.channel.send("You don't have permission for that command.");
}
}
}
return false;
}
Loading…
Cancel
Save