Working color module

This commit is contained in:
MSWS
2023-04-09 17:16:05 -07:00
parent cd7705d71b
commit a0cf61bdf9
8 changed files with 188 additions and 82 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/
dist/
*.js
*.js
config.json

View File

@@ -5,18 +5,27 @@
"enabled": false
}
],
"guild-roles": {
"react-roles": {
"enabled": true,
"channel": "123456789012345678",
"unique": true,
"channel": "1056498303329652768",
"embedData": {
"title": "Roles",
"description": "React to this message to get a role!",
"color": "0x00FF00"
"title": "Color Roles",
"description": "React to this message to get a color role!",
"color": 1
},
"tiers": [
"012345",
"012345",
"012345"
]
"roles": [
"1056498302360768547",
"1056498302360768546",
"1056498302360768545",
"1056498302360768544"
],
"emotes": [
"🔴",
"🟠",
"🟡",
"🟢"
],
"embedId": "1094769065358278806"
}
}

View File

@@ -9,7 +9,7 @@ import { Client } from "discord.js";
import { MaulConfig, MaulEntry } from "./data/configs/MAUL";
import { CommandManager } from "./modules/Commands";
import { PingCommand } from "./modules/commands/PingCommand";
import { FooCommand } from "./modules/commands/FooCommand";
import { ReactRoles } from "./modules/ReactRoles";
export class Bot implements DSBot {
maul: MaulAPI | undefined;
@@ -24,7 +24,7 @@ export class Bot implements DSBot {
const tokenEntry = this.config.registerEntry(new TokenEntry());
const maulEntry = this.config.registerEntry(new MaulEntry());
this.client = new Client({ intents: ["Guilds"] });
this.client = new Client({ intents: ["Guilds", "GuildMessageReactions"] });
this.config.load().then(() => {
this.config.getValue(tokenEntry).then(token => {
this.client.login(token as string);
@@ -44,11 +44,11 @@ export class Bot implements DSBot {
initModules() {
const cmds = this.modules.addModule(new CommandManager(this)) as CommandManager;
this.modules.addModule(new ReactRoles(this));
this.modules.loadModules();
cmds.registerCommand(new PingCommand());
cmds.registerCommand(new FooCommand());
cmds.hookCommand();
}

View File

@@ -5,8 +5,8 @@ import { GuildConfig } from "../api/data/GuildConfig";
export class FileConfig implements BotConfig {
entries: ConfigEntry[] = [];
file: string;
configs: GuildConfig[] = [];
file: string;
constructor(file: string) {
this.file = file;
@@ -21,7 +21,10 @@ export class FileConfig implements BotConfig {
}
getGuildConfig(guildId: string): Promise<GuildConfig> {
const config = new FileGuildConfig(`./guilds/${guildId}.json`, guildId);
const config = this.configs.find((c) => c.guildId === guildId);
if (!config)
return Promise.reject(new Error(`Guild config not found: ${guildId}`));
return Promise.resolve(config);
}
@@ -42,7 +45,6 @@ export class FileConfig implements BotConfig {
}
async load(): Promise<void> {
console.log("Loading config file: " + this.file);
const promise = new Promise<void>((resolve, reject) => {
fs.open(this.file, "r", (err, fd) => {
if (err) {
@@ -54,13 +56,11 @@ export class FileConfig implements BotConfig {
// Parse the file as JSON
const data = JSON.parse(fs.readFileSync(fd, "utf8"));
console.log("Parsed data: " + data);
console.log(JSON.stringify(data));
// For each key in the JSON object, create a new ConfigEntry
for (const key in data) {
const entry = this.getConfigEntry(key);
if (!entry) {
console.error("Config entry not found: " + key);
console.error(`Config entry not found: ${key}`);
continue;
}
@@ -71,10 +71,12 @@ export class FileConfig implements BotConfig {
});
});
const files = fs.readdirSync("./guilds");
for (const file of files) {
const config = new FileGuildConfig(`./guilds/${file}`, file.split(".")[0]);
this.configs.push(config);
if (this.configs.length === 0) {
const files = fs.readdirSync("./guilds");
for (const file of files) {
const config = new FileGuildConfig(`./guilds/${file}`, file.split(".")[0]);
this.configs.push(config);
}
}
return promise;
@@ -95,7 +97,8 @@ export class FileConfig implements BotConfig {
data[entry.name] = entry.value;
});
fs.writeFileSync(this.file, JSON.stringify(data));
fs.writeFileSync(this.file, JSON.stringify(data, null, 4));
return Promise.resolve();
}

View File

@@ -1,44 +0,0 @@
import { EmbedData, Role } from "discord.js";
import { DSBot } from "../api/DSBot";
import { Module } from "../api/Module";
import { GuildedConfigEntry } from "../api/data/Config";
export class ColorRoles extends Module {
name = "Color Roles";
constructor(main: DSBot) {
super(main);
}
onEnable(): void {
}
onDisable(): void {
}
}
export class ColorRolesConfig implements GuildedConfigEntry<ColorRolesConfigEntry> {
guildId: string;
name = "color-roles";
value: ColorRolesConfigEntry;
constructor(guildId: string) {
this.guildId = guildId;
this.value = {
enabled: false,
channel: "",
embedData: {},
tiers: []
};
}
onUpdate(oldValue: ColorRolesConfigEntry, newValue: ColorRolesConfigEntry): void {
this.value = newValue;
}
}
export interface ColorRolesConfigEntry {
enabled: boolean;
channel: string;
embedData: EmbedData;
tiers: Role[];
}

View File

@@ -46,7 +46,6 @@ export class CommandManager extends Module {
}
registerCommand(command: Command): void {
console.log("Registering command: " + command.data.name);
this.commands.set(command.data.name, command);
}
@@ -60,37 +59,28 @@ export class CommandManager extends Module {
const guildRegistrations = [];
const guilds = await this.main.client.guilds.fetch();
for (const weakGuild of guilds.values()) {
console.log("Registering commands for guild: " + weakGuild.name);
if (!this.configs.has(weakGuild.id))
this.configs.set(weakGuild.id, new Map());
guildRegistrations.push(
async () => {
const guildConfig = this.configs.get(weakGuild.id);
console.log(`${weakGuild.id} config: ${JSON.stringify(guildConfig)}`);
const deepGuild = await weakGuild.fetch();
for (const cmd of commands.filter(cmd => !cmd.global)) {
if (guildConfig?.has(cmd.data.name)) {
if (!guildConfig.get(cmd.data.name)?.enabled)
continue;
}
console.log("Registering Guild Command: " + cmd.data.name);
deepGuild.commands.create(cmd.data);
}
}
);
}
console.log("Awaiting completion of guild commands...");
await Promise.all(guildRegistrations.map(fn => fn())).catch(console.error);
console.log("Completed guild commands, registering global commands...");
for (const cmd of commands.filter(cmd => cmd.global)) {
console.log("Registering global command: " + cmd.data.name);
for (const cmd of commands.filter(cmd => cmd.global))
this.main.client.application?.commands.create(cmd.data);
}
console.log("Completed global commands.");
}
async onEnable(): Promise<void> {
@@ -99,7 +89,6 @@ export class CommandManager extends Module {
for (const guildConfig of guildConfigs) {
guildConfig.registerEntry(this.commandsConfigEntry);
console.log("Registered guild config: " + guildConfig.guildId);
processors.push(async () => {
const commandMap = this.configs.get(guildConfig.guildId) || new Map();

148
src/modules/ReactRoles.ts Normal file
View File

@@ -0,0 +1,148 @@
import { ChannelType, EmbedBuilder, EmbedData, EmojiResolvable, Guild, Message, MessageReaction, PartialMessageReaction, PartialUser, Role, User } from "discord.js";
import { DSBot } from "../api/DSBot";
import { Module } from "../api/Module";
import { GuildedConfigEntry } from "../api/data/Config";
export class ReactRoles extends Module {
name = "React Roles";
configs: Map<string, ColorRolesConfig> = new Map();
constructor(main: DSBot) {
super(main);
}
async onEnable(): Promise<void> {
const loadConfigs = [];
for (const guild of this.main.client.guilds.cache.values()) {
loadConfigs.push(async () => {
const config = await this.main.config.getGuildConfig(guild.id);
const colorEntry = config.registerEntry(new ColorRolesConfig(guild.id));
await config.load();
this.configs.set(guild.id, colorEntry as ColorRolesConfig);
});
}
await Promise.all(loadConfigs.map((f) => f()));
for (const config of this.configs.values()) {
if (config.value.enabled)
this.sendEmbedMessage(config.guildId);
}
// this.main.client.on("messageReactionAdd", this.onReactionAdd.bind(this));
this.main.client.on("messageReactionAdd", (reaction, user) => {
this.onReactionAdd(reaction, user);
});
}
async sendEmbedMessage(guild: Guild | string): Promise<void> {
if (typeof guild === "string")
guild = this.main.client.guilds.cache.get(guild) ?? await this.main.client.guilds.fetch(guild);
const config = this.configs.get(guild.id)?.value;
if (!config)
return;
const channel = guild.channels.cache.get(config.channel) ?? await guild.channels.fetch(config.channel);
if (!channel)
return;
if (channel.type !== ChannelType.GuildText)
return;
if (config.embedId) {
const preExistingEmbed = await channel.messages.fetch(config.embedId ?? "").catch(() => undefined);
if (preExistingEmbed) {
this.reactRoles(preExistingEmbed).catch(console.error);
return;
}
}
const embed = new EmbedBuilder(config.embedData);
const message = await channel.send({ embeds: [embed] });
config.embedId = message.id;
this.configs.get(guild.id)?.onUpdate(config, config);
(await this.main.config.getGuildConfig(guild.id)).save();
this.reactRoles(message).catch(console.error);
}
async reactRoles(message: Message) {
if (!message.guild || !message.guildId)
return;
const config = this.configs.get(message.guildId)?.value;
if (!config)
return;
for (const emote of config.emotes)
message.react(emote);
}
async onReactionAdd(reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser): Promise<void> {
if (user.id === this.main.client.user?.id)
return;
reaction.users.remove(user.id);
const guildUser = await reaction.message.guild?.members.fetch(user.id).catch(() => undefined);
if (!guildUser)
return;
const config = this.configs.get(reaction.message.guildId ?? "")?.value;
if (!config)
return;
if (config.unique) {
for (const role of config.roles) {
if (guildUser.roles.cache.has(role))
guildUser.roles.remove(role);
}
}
const role = config.roles[config.emotes.indexOf(reaction.emoji.toString())];
if (!role)
return;
console.log(role);
guildUser.roles.add(role);
}
onDisable(): void {
}
}
export class ColorRolesConfig implements GuildedConfigEntry<ReactRolesConfigEntry> {
guildId: string;
name = "react-roles";
value: ReactRolesConfigEntry;
constructor(guildId: string) {
this.guildId = guildId;
this.value = {
enabled: false,
channel: "",
embedId: undefined,
embedData: {},
roles: [],
emotes: [],
unique: false
};
}
onUpdate(oldValue: ReactRolesConfigEntry, newValue: ReactRolesConfigEntry): void {
this.value = newValue;
}
}
export interface ReactRolesConfigEntry {
enabled: boolean;
channel: string;
embedId: string | undefined;
embedData: EmbedData;
roles: string[];
emotes: EmojiResolvable[];
unique: boolean | undefined;
}

View File

@@ -47,7 +47,7 @@
// "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. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "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": "./dist/", /* Specify an output folder for all emitted files. */