WIP: feat/20-reactions-for-roles #59

Draft
kenobi wants to merge 16 commits from feat/20-reactions-for-roles into master
7 changed files with 125 additions and 44 deletions

View File

@ -82,41 +82,5 @@ Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für ein
}
export async function manageAnnouncementRoles(guild: Guild, reaction: MessageReaction, requestId: string) {
const guildId = guild.id
logger.info("Managing roles", { guildId, requestId })
const announcementRole: Role | undefined = (await guild.roles.fetch()).find(role => role.id === config.bot.announcement_role)
if (!announcementRole) {
logger.error(`Could not find announcement role! Aborting! Was looking for role with id: ${config.bot.announcement_role}`, { guildId, requestId })
return
}
const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user)
const allUsers = (await guild.members.fetch())
const usersWhoHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache
.find(role => role.id === config.bot.announcement_role) !== undefined)
.map(member => member)
const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
.filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
const usersWhoDontHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache
.find(role => role.id === config.bot.announcement_role) === undefined)
.map(member => member)
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, { guildId, requestId })
logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId })
usersWhoNeedRoleRevoked.forEach(user => user.roles.remove(announcementRole))
usersWhoNeedRole.forEach(user => user.roles.add(announcementRole))
}

View File

@ -22,7 +22,6 @@ export async function execute(messageReaction: MessageReaction, user: User) {
}
logger.info(`Got reaction on message`, { requestId, guildId })
//logger.debug(`reactedUponMessage payload: ${JSON.stringify(reactedUponMessage)}`)
logger.info(`emoji: ${messageReaction.emoji.toString()}`)
@ -39,7 +38,8 @@ export async function execute(messageReaction: MessageReaction, user: User) {
}
else if (isInitialAnnouncement(reactedUponMessage)) {
if (messageReaction.emoji.toString() === Emoji.ticket) {
logger.error(`Got a role emoji. Not implemented yet. ${reactedUponMessage.id}`)
logger.error(`Got a role emoji. ${reactedUponMessage.id}`)
return client.roleController.addMediaRoleToUser(user, messageReaction.message.guild, requestId)
}
return
}

View File

@ -0,0 +1,33 @@
import { Message, MessageReaction, User } from "discord.js";
import { logger, newRequestId, noGuildId } from "../logger";
import { Emoji } from "../constants";
import { client } from "../..";
import { isInitialAnnouncement } from "../helper/messageIdentifiers";
export const name = 'messageReactionRemove'
export async function execute(messageReaction: MessageReaction, user: User) {
if (user.id == client.user?.id) {
logger.info('Skipping bot reaction')
return
}
const requestId = newRequestId()
const guildId = messageReaction.message.inGuild() ? messageReaction.message.guildId : noGuildId
const reactedUponMessage: Message = messageReaction.message.partial ? await messageReaction.message.fetch() : messageReaction.message
if (!messageReaction.message.guild) {
logger.warn(`Received messageReactionRemove on non-guild message.`, { requestId })
return
}
logger.info(`Got reaction on message`, { requestId, guildId })
logger.info(`emoji: ${messageReaction.emoji.toString()}`)
if (isInitialAnnouncement(reactedUponMessage)) {
if (messageReaction.emoji.toString() === Emoji.ticket) {
logger.info(`User: ${user.id}, ${user.username} has removed a ticket reaction. Starting role management`, { requestId, guildId })
return client.roleController.removeMediaRoleFromUser(user, messageReaction.message.guild, requestId)
}
}
}

View File

@ -0,0 +1,83 @@
import { Guild, MessageReaction, Role, User } from "discord.js";
import { GuildMember } from "discord.js";
import { logger } from "../logger";
import { config } from "../configuration";
import { Maybe } from "../interfaces";
export default class RoleController {
constructor() { }
private getAnnounceRoleIdForGuild(guildId: string): string {
const role = config.bot.announcement_role
if (!role) throw new Error(`No announcementRole defined for guild ${guildId}`)
return role
}
public async addRoleToUser(member: GuildMember, role: Role, guildId: string, requestId: string) {
logger.info(`Adding Role ${role.id} to user ${member.id}|${member.user.username}`, { requestId, guildId })
return await member.roles.add(role)
}
private async removeRoleFromUser(member: GuildMember, role: Role, guildId: string, requestId: string) {
logger.info(`Removing Role ${role.id} from user ${member.id}|${member.user.username}`, { requestId, guildId })
return await member.roles.remove(role)
}
public async addMediaRoleToUser(user: User, guild: Guild, requestId: string) {
const roleToAdd = await this.getAnnouncementRoleForGuild(guild, requestId)
if (!roleToAdd) throw new Error(`No announcementRole found to add to user`)
const guildMember = await guild.members.fetch(user)
return this.addRoleToUser(guildMember, roleToAdd, guild.id, requestId)
}
public async removeMediaRoleFromUser(user: User, guild: Guild, requestId: string) {
const roleToRemove = await this.getAnnouncementRoleForGuild(guild, requestId)
if (!roleToRemove) throw new Error(`No announcementRole found to remove from user`)
const guildMember = await guild.members.fetch(user)
return this.removeRoleFromUser(guildMember, roleToRemove, guild.id, requestId)
}
public async getAnnouncementRoleForGuild(guild: Guild, requestId: string): Promise<Role> {
const mediaRole = this.getAnnounceRoleIdForGuild(guild.id)
const announcement_role = await guild.roles.fetch()
.then(fetchedRoles => fetchedRoles.find(role => role.id === mediaRole))
.catch(error => {
logger.error(`Could not find announcement_role with id ${config.bot.announcement_role}. Error: ${error}`, { requestId, guildId: guild.id })
throw error
})

method called reactions but parameter is singular

method called reactions but parameter is singular
1f372b0aacd94d6eb5cfbe2e72f61dfdf77c1a31
if (!announcement_role) throw new Error(`Could not find announcement_role with id ${config.bot.announcement_role}.`)
return announcement_role
}
public async assignAnnouncementRolesFromReaction(guild: Guild, reaction: MessageReaction, requestId: string) {
const guildId = guild.id
Review

add error to previous line to be consistent with rest of class. Also maybe put checking to getAnnouncementRoleForGuild, so calling methods can rely on the role to be there?

add error to previous line to be consistent with rest of class. Also maybe put checking to getAnnouncementRoleForGuild, so calling methods can rely on the role to be there?
Review
a50ac1716f5eccf38d88763742d26e573b460dca
logger.info("Managing roles", { guildId, requestId })
const announcementRole = await this.getAnnouncementRoleForGuild(guild, requestId)
const usersWhoWantRole: User[] = (await reaction.users.fetch()).filter(user => !user.bot).map(user => user)
const allUsers = await guild.members.fetch()
const usersWhoHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache
.find(role => role.id === announcementRole.id) !== undefined)
.map(member => member)
const usersWhoNeedRoleRevoked: GuildMember[] = usersWhoHaveRole
.filter(userWhoHas => !usersWhoWantRole.map(wanter => wanter.id).includes(userWhoHas.id))
const usersWhoDontHaveRole: GuildMember[] = allUsers
.filter(member => member.roles.cache
.find(role => role.id === announcementRole.id) === undefined)
.map(member => member)
const usersWhoNeedRole: GuildMember[] = usersWhoDontHaveRole
.filter(userWhoNeeds => usersWhoWantRole.map(wanter => wanter.id).includes(userWhoNeeds.id))
logger.debug(`Theses users will get the role removed: ${JSON.stringify(usersWhoNeedRoleRevoked)}`, { guildId, requestId })
logger.debug(`Theses users will get the role added: ${JSON.stringify(usersWhoNeedRole)}`, { guildId, requestId })
usersWhoNeedRoleRevoked.forEach(user => this.removeRoleFromUser(user, announcementRole, guild.id, requestId))
usersWhoNeedRole.forEach(user => this.addRoleToUser(user, announcementRole, guild.id, requestId))
}
}

View File

@ -5,7 +5,7 @@ import { getMembersWithRoleFromGuild } from "./roleFilter"
import { config } from "../configuration"
import { VoteMessage, isVoteEndedMessage, isVoteMessage } from "./messageIdentifiers"
import { createDateStringFromEvent } from "./dateHelper"
import { Maybe, voteMessageInputInformation as prepareVoteMessageInput } from "../interfaces"
import { Maybe, prepareVoteMessageInput } from "../interfaces"
import format from "date-fns/format"
import toDate from "date-fns/toDate"
import differenceInDays from "date-fns/differenceInDays"

View File

@ -39,7 +39,7 @@ export interface JellyfinConfig {
collectionUser: string
}
export type PermissionLevel = "VIEWER" | "ADMIN" | "TEMPORARY"
export interface voteMessageInputInformation {
export interface prepareVoteMessageInput {
movies: string[],
startDate: Date,
event: GuildScheduledEvent,

View File

@ -2,7 +2,6 @@ import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, Gu
import fs from 'fs';
import { ScheduledTask, schedule } from "node-cron";
import { v4 as uuid } from 'uuid';
import { manageAnnouncementRoles } from "../commands/announce";
import { config } from "../configuration";
import { Maybe } from "../interfaces";
import { JellyfinHandler } from "../jellyfin/handler";
@ -11,6 +10,7 @@ import { CommandType } from "../types/commandTypes";
import { isInitialAnnouncement } from "../helper/messageIdentifiers";
import VoteController from "../helper/vote.controller";
import { yavinJellyfinHandler } from "../..";
import RoleController from "../helper/role.controller";
@ -19,6 +19,7 @@ export class ExtendedClient extends Client {
private commandFilePath = `${__dirname}/../commands`
private jellyfin: JellyfinHandler
public voteController: VoteController = new VoteController(this, yavinJellyfinHandler)
public roleController: RoleController = new RoleController()
public commands: Collection<string, CommandType> = new Collection()
private announcementChannels: Collection<string, TextChannel> = new Collection() //guildId to TextChannel
private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection() //one task per guild
@ -172,10 +173,10 @@ export class ExtendedClient extends Client {
}
//logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId })
const reactions = message.reactions.resolve("🎫")
const ticketReaction = message.reactions.resolve("🎫")
//logger.debug(`reactions: ${JSON.stringify(reactions, null, 2)}`, { guildId: guild.id, requestId })
if (reactions) {
manageAnnouncementRoles(message.guild, reactions, requestId)
if (ticketReaction) {
this.roleController.assignAnnouncementRolesFromReaction(message.guild, ticketReaction, requestId)
} else {
logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId })
}