Compare commits

..

2 Commits

3 changed files with 100 additions and 9 deletions

View File

@ -1,11 +1,14 @@
import { Guild, GuildMember, GuildScheduledEvent, GuildScheduledEventEditOptions, GuildScheduledEventSetStatusArg, GuildScheduledEventStatus, Message, MessageCreateOptions, MessageEditOptions, TextChannel, messageLink } from 'discord.js' import { Guild, GuildMember, Message, MessageCreateOptions, MessageReaction, Role, TextChannel, User } from 'discord.js'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { client } from '../..'
import { config } from '../configuration' import { config } from '../configuration'
import { Emotes } from '../events/guildScheduledEventCreate'
import { logger } from '../logger' import { logger } from '../logger'
import { Command } from '../structures/command' import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes' import { RunOptions } from '../types/commandTypes'
import { client } from '../..' import { off } from 'process'
import { ScheduledTask, schedule } from 'node-cron'
let task: ScheduledTask
export default new Command({ export default new Command({
name: 'announce', name: 'announce',
@ -29,14 +32,18 @@ export default new Command({
}) })
function isAdmin(member: GuildMember): boolean { function isAdmin(member: GuildMember): boolean {
return member.roles.cache.find((role, _) => role.id === config.bot.jf_admin_role) != undefined return member.roles.cache.find((role, _) => role.id === config.bot.jf_admin_role) !== undefined
} }
async function sendAnnouncement(guildId: string, requestId: string): Promise<void> { async function sendAnnouncement(guildId: string, requestId: string): Promise<void> {
logger.info("Sending announcement") logger.info("Sending announcement")
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId) const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId)
const body = `Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett. const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[announcement]"))
currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())
currentPinnedAnnouncementMessages.forEach(message => message.delete())
const body = `[announcement] Hey! @everyone! Hier ist der Watchparty Bot vom Hartzarett.
Wir machen in Zukunft regelmäßig Watchparties! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt. Wir machen in Zukunft regelmäßig Watchparties! Falls du mitmachen möchtest, reagiere einfach auf diesen Post mit 🎫, dann bekommst du automatisch eine Rolle zugewiesen und wirst benachrichtigt sobald es in der Zukunft weitere Watchparties und Filme zum abstimmen gibt.
@ -47,5 +54,46 @@ Für eine Erklärung wie das alles funktioniert mach einfach /mitgucken für ein
content: body content: body
} }
const message: Message<true> = await announcementChannel.send(options) const message: Message<true> = await announcementChannel.send(options)
message.react("🎫") await message.react("🎫")
} await message.pin()
}
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

@ -23,6 +23,7 @@ export interface Config {
workaround_token: string workaround_token: string
watcher_role: string watcher_role: string
jf_admin_role: string jf_admin_role: string
announcement_role: string
announcement_channel_id: string announcement_channel_id: string
jf_collection_id: string jf_collection_id: string
} }
@ -57,6 +58,7 @@ export const config: Config = {
workaround_token: process.env.TOKEN ?? "", workaround_token: process.env.TOKEN ?? "",
watcher_role: process.env.WATCHER_ROLE ?? "", watcher_role: process.env.WATCHER_ROLE ?? "",
jf_admin_role: process.env.ADMIN_ROLE ?? "", jf_admin_role: process.env.ADMIN_ROLE ?? "",
announcement_role: process.env.WATCHPARTY_ANNOUNCEMENT_ROLE ?? "",
announcement_channel_id: process.env.CHANNEL_ID ?? "", announcement_channel_id: process.env.CHANNEL_ID ?? "",
jf_collection_id: process.env.JELLYFIN_COLLECTION_ID ?? "" jf_collection_id: process.env.JELLYFIN_COLLECTION_ID ?? ""
} }

View File

@ -4,6 +4,10 @@ import fs from 'fs'
import { config } from "../configuration"; import { config } from "../configuration";
import { logger } from "../logger"; import { logger } from "../logger";
import { JellyfinHandler } from "../jellyfin/handler"; import { JellyfinHandler } from "../jellyfin/handler";
import { ScheduledTask, schedule } from "node-cron";
import { manageAnnouncementRoles } from "../commands/announce";
import { v4 as uuid } from 'uuid'
import { task } from "../events/guildScheduledEventCreate";
@ -13,6 +17,7 @@ export class ExtendedClient extends Client {
private jellyfin: JellyfinHandler private jellyfin: JellyfinHandler
public commands: Collection<string, CommandType> = new Collection() public commands: Collection<string, CommandType> = new Collection()
private announcementChannels: Collection<string, TextChannel> = new Collection //guildId to TextChannel private announcementChannels: Collection<string, TextChannel> = new Collection //guildId to TextChannel
private announcementRoleHandlerTask: Collection<string, ScheduledTask> = new Collection //one task per guild
public constructor(jf: JellyfinHandler) { public constructor(jf: JellyfinHandler) {
const intents: IntentsBitField = new IntentsBitField() const intents: IntentsBitField = new IntentsBitField()
intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates) intents.add(IntentsBitField.Flags.GuildMembers, IntentsBitField.Flags.MessageContent, IntentsBitField.Flags.Guilds, IntentsBitField.Flags.DirectMessages, IntentsBitField.Flags.GuildScheduledEvents, IntentsBitField.Flags.GuildVoiceStates)
@ -60,14 +65,15 @@ export class ExtendedClient extends Client {
this.commands.set(command.name, command) this.commands.set(command.name, command)
slashCommands.push(command) slashCommands.push(command)
} }
this.on("ready", (client: Client) => { this.on("ready", async (client: Client) => {
//logger.info(`Ready processing ${JSON.stringify(client)}`) //logger.info(`Ready processing ${JSON.stringify(client)}`)
logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`) logger.info(`SlashCommands: ${JSON.stringify(slashCommands)}`)
const guilds = client.guilds.cache const guilds = client.guilds.cache
this.registerCommands(slashCommands, guilds) this.registerCommands(slashCommands, guilds)
this.cacheUsers(guilds) this.cacheUsers(guilds)
this.cacheAnnouncementServer(guilds) await this.cacheAnnouncementServer(guilds)
this.startAnnouncementRoleBackgroundTask(guilds)
}) })
} catch (error) { } catch (error) {
logger.info(`Error refreshing slash commands: ${error}`) logger.info(`Error refreshing slash commands: ${error}`)
@ -117,4 +123,39 @@ export class ExtendedClient extends Client {
logger.error(error) logger.error(error)
} }
} }
public async startAnnouncementRoleBackgroundTask(guilds: Collection<string, Guild>) {
for (const guild of guilds.values()) {
logger.info("Starting background task for announcement role", { guildId: guild.id })
const textChannel: TextChannel = this.getAnnouncementChannelForGuild(guild.id)
this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
const requestId = uuid()
const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[announcement]"))
if (messages.size > 1) {
logger.error("More than one pinned announcement Messages found. Unable to know which one people react to. Please fix!", { guildId: guild.id, requestId })
} else if (messages.size == 0) {
logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId })
}
const message = messages.at(0)!
const reactions = await message.reactions.resolve("🎫")
if (reactions) {
manageAnnouncementRoles(message.guild, reactions, requestId)
} else {
logger.error("Did not get reactions! Aborting!", { guildId: guild.id, requestId })
}
}))
}
}
public stopAnnouncementRoleBackgroundTask(guild: string | Guild, requestId: string) {
const guildId: string = guild instanceof Guild ? guild.id : guild
const task: ScheduledTask | undefined = guild instanceof Guild ? this.announcementRoleHandlerTask.get(guild.id) : this.announcementRoleHandlerTask.get(guild)
if (!task) {
logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId })
return
}
task.stop()
}
} }