Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #39

Merged
merged 4 commits into from
Sep 17, 2023
Merged

Dev #39

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ COPY --from=build /app/bot/dist /app/bot
COPY --from=build /app/bot/package.json /app/bot
COPY ./templates /app/templates

RUN npm install --production
RUN npm install --omit=dev

CMD [ "node", "index.js" ]
98 changes: 92 additions & 6 deletions bot/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
Client,
Role,
Partials,
Snowflake,
Emoji,
GuildTextBasedChannel,
} from "discord.js"

import {
Expand All @@ -31,6 +34,9 @@ import {
OnReactionAddAction,
OnReactionRemoveAction,
} from "./types"
import {
MessageReaction
} from "./models"

const REST_VERSION = "10"

Expand All @@ -41,7 +47,7 @@ export class Bot {
buttonCommands = new Map<string, ICommand<ButtonCommand>>()
modalCommands = new Map<string, ICommand<ModalCommand>>()
dropdownCommands = new Map<string, IDropdownCommand<DropdownCommand>>()
reactionMessages = new Map<Message, Map<String, Role>>()
reactionMessages = new Map<Snowflake, Map<string, string>>()
private async onReady(args: OnReadyArgs): Promise<void> {
throw new Error("Event 'onReady' is not implementet")
}
Expand Down Expand Up @@ -87,9 +93,9 @@ export class Bot {
if (config.modalCommands) this.initModalCommands(config.modalCommands)
if (config.dropdownCommands) this.initDropdownCommands(config.dropdownCommands)

/* if (config.reactionMessages) {
this.initReactionMessages(config.reactionMessages);
} */
this.loadReactionMessages().then(value => {
this.reactionMessages = value
})

this.initEvents(config)
this.init()
Expand Down Expand Up @@ -137,6 +143,7 @@ export class Bot {
modals: this.modalCommands,
dropdown: this.dropdownCommands,
db: this.db,
bot: this,
mailer: this.mailer,
commandRegistration: this.registerChatInputGuildCommands,
}
Expand Down Expand Up @@ -187,10 +194,9 @@ export class Bot {
private initOnReactionAdd() {
this.client.on("messageReactionAdd", async (messageReaction, user) => {
const args: OnReactionAddArgs = {
client: this.client,
reaction: messageReaction,
user: user,
db: this.db,
reactionMessages: this.reactionMessages,
}

try {
Expand Down Expand Up @@ -248,6 +254,86 @@ export class Bot {
})
}

/**
* Loads reaction messages from the database
* @returns Promise that resolves to reaction messages
*/
private async loadReactionMessages(): Promise<Map<Snowflake, Map<string, string>>> {
const ReactionMessages = new Map<Snowflake, Map<string, string>>();

const allReactions = await MessageReaction.find({
select: {
messageId: true,
channelId: true
}
});

// Gets mask of boolean values
const uniqueMask = allReactions
.map(item => item.messageId)
.map((value, index, array) => array.indexOf(value) === index);

// Gets all unique MessageReactions
const uniqueMessage = allReactions.filter((_, index) => uniqueMask[index]);

uniqueMessage.forEach(async ({messageId, channelId}) => {
const messageReactionsBinds = await MessageReaction.find({
select: {
emoji: true,
role: true,
},
where: {
messageId: messageId
}
});

const guild = await this.client.guilds.fetch(this.guildId)
const channel = (await guild.channels.fetch(channelId)!) as GuildTextBasedChannel
const message = await channel.messages.fetch(messageId)

if(message === null){
throw new Error("Invalid message")
}

// Clean old reactions before adding new ones
await message.reactions.removeAll()

// Adding new reactions based on binds specified in database
const reactionBinds = new Map()
messageReactionsBinds.forEach(async row => {
reactionBinds.set(row.emoji, row.role)
await message.react(row.emoji)
});

ReactionMessages.set(messageId, reactionBinds)
});

return ReactionMessages
}

/**
* Adds a new reaction message to the bot config
* @param messageId Reaction message id
* @param reactions Binds that should be added for the message
*/
public async addReactionMessage(messageId: string, channelId: string, reactions: Map<string, string>) {
// Updating new reactions binds
this.reactionMessages.set(messageId, reactions)

// Removing reactions binds
await MessageReaction.delete({ messageId: messageId })

// Adding new reaction binds
reactions.forEach(async (role, emoji) => {
const messageReaction = new MessageReaction()
messageReaction.messageId = messageId
messageReaction.emoji = emoji
messageReaction.role = role
messageReaction.channelId = channelId
await messageReaction.save();
});
}

public async login() {
if (this.isLogedIn)
return
Expand Down
11 changes: 8 additions & 3 deletions bot/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
StringSelectMenuInteraction,
RESTPostAPIApplicationCommandsJSONBody, // TODO: Update to latest discord.js
} from "discord.js"

import {
Bot
} from "./bot"
import {
InvalidChannel,
InvalidGuild,
Expand Down Expand Up @@ -52,10 +54,12 @@ class Command {
description!: string
client!: Client
mailer!: Mailer
bot!: Bot

async execute(client: Client, mailer: Mailer): Promise<void> {
async execute(client: Client, mailer: Mailer, bot: Bot): Promise<void> {
this.client = client
this.mailer = mailer
this.bot = bot

await this.executable()
}
Expand All @@ -69,10 +73,11 @@ export class InteractionCommand<T extends Interaction> extends Command {
interaction!: T

// @ts-ignore
async execute(client: Client, mailer: Mailer, interaction: T): Promise<void> {
async execute(client: Client, mailer: Mailer, bot: Bot, interaction: T): Promise<void> {
this.client = client
this.mailer = mailer
this.interaction = interaction
this.bot = bot

try {
await this.executable()
Expand Down
39 changes: 30 additions & 9 deletions bot/src/commands/messageManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from "axios"

import { ActionRowBuilder, SelectMenuBuilder, SlashCommandBuilder } from "@discordjs/builders"
import { ButtonBuilder, TextBasedChannel } from "discord.js"
import { ButtonBuilder, TextBasedChannel} from "discord.js"


import { TextFile, TextFileMessage } from "../interfaces"
Expand All @@ -22,7 +22,6 @@ import {
InvalidURLError,
} from "../errors"


const maxMessageLength = 2000
const channelTagName = "channel"
const mentionTagName = "mention"
Expand Down Expand Up @@ -213,14 +212,15 @@ export class MessageManagerCommand extends ChatInputCommand {
throw new InvalidTextBasedChannel()

for (const rawMessage of data.messages)
await this.processOneMessage(rawMessage, channel,)
await this.processOneMessage(rawMessage, channel)
}

async processOneMessage(rawMessage: TextFileMessage, channel: TextBasedChannel): Promise<void> {
const messageId = rawMessage.id
const message = await channel.messages.fetch(messageId)

if (!message)
throw "".toError()
throw `Zpráva s id ${messageId} nebyla nalezena v kanále s id ${channel.id}`.toError()
if (message.author !== this.client.user)
throw new BotCanEditOnlySelfMessagesError()

Expand All @@ -244,20 +244,41 @@ export class MessageManagerCommand extends ChatInputCommand {
rows.push(row)
}

// Reactions
const reactionMap = new Map<string, string>()
if (rawMessage.reactions) {

for (const [key, value] of Object.entries(rawMessage.reactions)) {
reactionMap.set(value, key)
}

await this.bot.addReactionMessage(rawMessage.id, channel.id, reactionMap)

// Clean old reactions before adding new ones
await message.reactions.removeAll()

reactionMap.forEach(async (role, emoji) => {
await message.react(emoji)
});
}

const unparsedContent = rawMessage.content.join("\n")
const content = this.handleMentions(unparsedContent)
let content = this.handleMentions(unparsedContent)

if (rows.length > 0) {
// Adds list of reaction and assigned roles
reactionMap.forEach((role, emoji) => {
content += `\n- ${emoji} - ${role}`
})

if (rows.length > 0) {
await message.edit({
content: content,
// @ts-ignore
components: rows
})
return
} else {
await message.edit({ content: content })
}

await message.edit({ content: content })
}

createButtonComponent(raw: { id: string; label: string; style: string; }) {
Expand Down
6 changes: 0 additions & 6 deletions bot/src/commands/modals/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { ModalCommand } from "../../command";
import { Validation } from "../../models";

import {
isValidateEmail,
isUpolEmail,
makeRegisterText,
makeRegisterHTML
} from "../../utils";
import {
InvalidEmailFormatError,
UnknownUpolEmailError,
} from "../../errors";
import { VerificationCodeButton } from "../../buttons/verificationCode";
Expand All @@ -25,10 +23,6 @@ export class VerificationModalCommand extends ModalCommand {

email = email.trim()

// validace emailu
if (!isValidateEmail(email))
throw new InvalidEmailFormatError(email as string)

// validace domény emailu
if (!isUpolEmail(email))
throw new UnknownUpolEmailError(email)
Expand Down
4 changes: 3 additions & 1 deletion bot/src/databaseSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { DataSource } from "typeorm"
import {
Validation,
Error,
User
User,
MessageReaction,
} from "./models"

const {
Expand All @@ -27,6 +28,7 @@ export const DatabaseSource = new DataSource({
Validation,
Error,
User,
MessageReaction,
],
synchronize: true,
logging: false,
Expand Down
8 changes: 1 addition & 7 deletions bot/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,9 @@ export class InvalidGuild extends Error {
}
}

export class InvalidEmailFormatError extends Error {
constructor(email: string) {
super(`Email není ve správném tvaru ${email}.`)
}
}

export class UnknownUpolEmailError extends Error {
constructor(email: string) {
super(`${email} napatří do domény Univerzity Palackého. Registrace je jen pro emaily typu \`uživatel@upol.cz\`.`)
super(`\`${email}\` napatří do domény Univerzity Palackého. Registrace je jen pro emaily typu \`jmeno.prijmeniXX@upol.cz\`.`)
}
}

Expand Down
4 changes: 1 addition & 3 deletions bot/src/events/onGuildMemberAdd.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { OnGuildMemberAddAction } from "../types"

export const onGuildMemberAdd: OnGuildMemberAddAction = async ({ member }) => {

}
export const onGuildMemberAdd: OnGuildMemberAddAction = async ({ member }) => {}
4 changes: 2 additions & 2 deletions bot/src/events/onInteractionCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UnknownCommandError } from "../errors"
import { InteractionCommand } from "../command"

export async function onInteractionCreate(args: OnInteractionCreateArgs) {
const { client, interaction, mailer, commands, buttons, modals, dropdown } = args
const { client, interaction, mailer, bot, commands, buttons, modals, dropdown } = args
let command: InteractionCommand<Interaction<CacheType>> | undefined

if (interaction.isButton()) {
Expand Down Expand Up @@ -40,5 +40,5 @@ export async function onInteractionCreate(args: OnInteractionCreateArgs) {
throw new UnknownCommandError()
}

await command.execute(client, mailer, interaction)
await command.execute(client, mailer, bot, interaction)
}
33 changes: 30 additions & 3 deletions bot/src/events/onReactionAdd.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import { OnReactionAddAction } from "../types"

export const onReactionAdd: OnReactionAddAction = async ({ reaction, user }) => {
console.log(`${user.username} použil reakci ${reaction.emoji}!`)
}
export const onReactionAdd: OnReactionAddAction = async ({ reactionMessages, reaction, user }) => {
if(user.bot){
return;
}

const messageId = reaction.message.id
if(!reactionMessages.has(messageId)){
return;
}

const removeAction = reaction.users.remove(user.id)

const reactionBinds = reactionMessages.get(messageId)!
const guild = reaction.message.guild!
const roleName = reactionBinds.get(reaction.emoji.toString())

if(roleName !== undefined){
const role = await guild.roles.cache.find(role => role.name === roleName)!.id
const gulidMember = await guild.members.fetch(user.id)

if(gulidMember.roles.cache.has(role)){
await gulidMember.roles.remove(role)
}
else {
await gulidMember.roles.add(role)
}
}

await removeAction;
}
Loading