announcements #18

Merged
kenobi merged 13 commits from feat/announce into master 2023-06-15 22:05:20 +02:00
11 changed files with 86 additions and 64 deletions
Showing only changes of commit 5b99c843b4 - Show all commits

View File

@ -2,13 +2,10 @@ import { ApplicationCommandOptionType, Guild, GuildMember, Message, MessageCreat
import { v4 as uuid } from 'uuid'
import { client } from '../..'
import { config } from '../configuration'
import { Maybe } from '../interfaces'
import { logger } from '../logger'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { off } from 'process'
import { ScheduledTask, schedule } from 'node-cron'
let task: ScheduledTask
export default new Command({
name: 'announce',
@ -53,12 +50,16 @@ export default new Command({
})
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 sendInitialAnnouncement(guildId: string, requestId: string): Promise<void> {
logger.info("Sending initial announcement")
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId)
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId, requestId })
return
}
const currentPinnedAnnouncementMessages = (await announcementChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
currentPinnedAnnouncementMessages.forEach(async (message) => await message.unpin())

View File

@ -7,6 +7,7 @@ import { logger } from '../logger'
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { format } from 'date-fns'
import { Maybe } from '../interfaces'
export default new Command({
name: 'closepoll',
@ -15,14 +16,14 @@ export default new Command({
run: async (interaction: RunOptions) => {
const command = interaction.interaction
const requestId = uuid()
const guildId = command.guildId!
logger.info("Got command for closing poll!", { guildId, requestId })
if (!command.guild) {
logger.error("No guild found in interaction. Cancelling closing request", { guildId, requestId })
logger.error("No guild found in interaction. Cancelling closing request", { requestId })
command.followUp("Es gab leider ein Problem. Ich konnte deine Anfrage nicht bearbeiten :(")
return
}
const guildId = command.guildId
logger.info("Got command for closing poll!", { guildId, requestId })
command.followUp("Alles klar, beende die Umfrage :)")
closePoll(command.guild, requestId)
}
@ -32,10 +33,14 @@ export async function closePoll(guild: Guild, requestId: string) {
const guildId = guild.id
logger.info("stopping poll", { guildId, requestId })
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(guildId)
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(guildId)
if(!announcementChannel) {
logger.error("Could not find the textchannel. Unable to close poll.", { guildId, requestId })
return
}
const messages: Message<true>[] = (await announcementChannel.messages.fetch()) //todo: fetch only pinned messages
.map((value, _) => value)
.map((value) => value)
.filter(message => !message.cleanContent.includes("[Abstimmung beendet]") && message.cleanContent.includes("[Abstimmung]"))
.sort((a, b) => b.createdTimestamp - a.createdTimestamp)
@ -60,7 +65,7 @@ export async function closePoll(guild: Guild, requestId: string) {
await lastMessage.delete()
const event = await getEvent(guild, guild.id, requestId)
if(event) {
updateEvent(event, votes, guild!, guildId, requestId)
updateEvent(event, votes, guild, guildId, requestId)
sendVoteClosedMessage(event, votes[0].movie, guildId, requestId)
}
@ -77,6 +82,10 @@ async function sendVoteClosedMessage(event: GuildScheduledEvent, movie: string,
}
const announcementChannel = client.getAnnouncementChannelForGuild(guildId)
logger.info("Sending vote closed message.", { guildId, requestId })
if(!announcementChannel) {
logger.error("Could not find announcement channel. Please fix!", { guildId, requestId })
return
}
announcementChannel.send(options)
}
@ -93,7 +102,7 @@ async function updateEvent(voteEvent: GuildScheduledEvent, votes: Vote[], guild:
async function getEvent(guild: Guild, guildId: string, requestId: string): Promise<GuildScheduledEvent | null> {
const voteEvents = (await guild.scheduledEvents.fetch())
.map((value, _) => value)
.map((value) => value)
.filter(event => event.name.toLowerCase().includes("voting offen"))
logger.debug(`Found events: ${JSON.stringify(voteEvents, null, 2)}`, { guildId, requestId })
@ -119,14 +128,14 @@ async function getVotesByEmote(message: Message, guildId: string, requestId: str
const reaction = await message.reactions.resolve(emote)
logger.debug(`Reaction for emote ${emote}: ${JSON.stringify(reaction, null, 2)}`, { guildId, requestId })
if (reaction) {
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote, guildId, requestId) }
const vote: Vote = { emote: emote, count: reaction.count, movie: extractMovieFromMessageByEmote(message, emote) }
votes.push(vote)
}
}
return votes
}
function extractMovieFromMessageByEmote(message: Message, emote: string, guildId: string, requestId: string): string {
function extractMovieFromMessageByEmote(message: Message, emote: string): string {
const lines = message.cleanContent.split("\n")
const emoteLines = lines.filter(line => line.includes(emote))

View File

@ -1,8 +1,7 @@
import { ApplicationCommandOptionType, BurstHandlerMajorIdKey } from 'discord.js'
import { v4 as uuid } from 'uuid'
import { jellyfinHandler } from "../.."
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
import { jellyfinHandler } from "../.."
import { v4 as uuid } from 'uuid'
export default new Command({
name: 'passwort_reset',

View File

@ -1,5 +1,4 @@
import dotenv from "dotenv"
import { AddListingProviderRequestToJSON } from "./jellyfin"
dotenv.config()
interface options {

View File

@ -1,3 +1,5 @@
import { addDays, format, isAfter } from "date-fns";
import toDate from "date-fns/fp/toDate";
import { GuildScheduledEvent, Message, MessageCreateOptions, TextChannel } from "discord.js";
import { ScheduledTask, schedule } from "node-cron";
import { v4 as uuid } from "uuid";
@ -5,8 +7,7 @@ import { client, jellyfinHandler } from "../..";
import { closePoll } from "../commands/closepoll";
import { config } from "../configuration";
import { logger } from "../logger";
import toDate from "date-fns/fp/toDate";
import { addDays, format, isAfter, isBefore } from "date-fns";
import { Maybe } from "../interfaces";
export const name = 'guildScheduledEventCreate'
@ -28,7 +29,11 @@ export async function execute(event: GuildScheduledEvent) {
logger.info(`Got ${movies.length} random movies. Creating voting`, { guildId: event.guildId, requestId })
logger.debug(`Movies: ${JSON.stringify(movies.map(movie => movie.name))}`, { guildId: event.guildId, requestId })
const announcementChannel: TextChannel = client.getAnnouncementChannelForGuild(event.guildId)
const announcementChannel: Maybe<TextChannel> = client.getAnnouncementChannelForGuild(event.guildId)
if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: event.guildId, requestId })
return
}
logger.debug(`Found channel ${JSON.stringify(announcementChannel, null, 2)}`, { guildId: event.guildId, requestId })
if(!event.scheduledStartAt) {
@ -40,7 +45,7 @@ export async function execute(event: GuildScheduledEvent) {
let message = `[Abstimmung]\n<@&${config.bot.announcement_role}> Es gibt eine neue Abstimmung für die nächste Watchparty am ${date} um ${time}}! Stimme hierunter für den nächsten Film ab!\n`
for (let i = 0; i < movies.length; i++) {
message = message.concat(Emotes[i]).concat(": ").concat(movies[i].name!).concat("\n")
message = message.concat(Emotes[i]).concat(": ").concat(movies[i].name ?? "Film hatte keinen Namen :(").concat("\n")
}
const options: MessageCreateOptions = {
@ -72,7 +77,7 @@ async function checkForPollsToClose(event: GuildScheduledEvent): Promise<void> {
//refetch event in case the time changed or the poll is already closed
const events = (await event.guild.scheduledEvents.fetch())
.filter(event => event.name.toLowerCase().includes("voting offen"))
.map((value, _) => value)
.map((value) => value)
if (!events || events.length <= 0) {
logger.info("Did not find any events. Cancelling", { guildId: event.guildId, requestId })

View File

@ -3,7 +3,6 @@ import { v4 as uuid } from "uuid";
import { client, jellyfinHandler } from "../..";
import { getGuildSpecificTriggerRoleId } from "../helper/roleFilter";
import { logger } from "../logger";
import { manageAnnouncementRoles } from "../commands/announce";
export const name = 'guildScheduledEventUpdate'
@ -11,24 +10,33 @@ export const name = 'guildScheduledEventUpdate'
export async function execute(oldEvent: GuildScheduledEvent, newEvent: GuildScheduledEvent) {
try {
const requestId = uuid()
logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`,{guildId: newEvent.guildId, requestId})
logger.debug(`Got scheduledEvent update. New Event: ${JSON.stringify(newEvent, null, 2)}`, { guildId: newEvent.guildId, requestId })
if (!newEvent.guild) {
logger.error("Event has no guild, aborting.", { guildId: newEvent.guildId, requestId })
return
}
if (newEvent.description?.toLowerCase().includes("!wp") && [GuildScheduledEventStatus.Active, GuildScheduledEventStatus.Completed].includes(newEvent.status)) {
const roles = getGuildSpecificTriggerRoleId(newEvent.guildId).map((key, value)=> value)
const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value, _) => value.member)
const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value, _) => value )
const roles = getGuildSpecificTriggerRoleId().map((key, value) => value)
const eventMembers = (await newEvent.fetchSubscribers({ withMember: true })).filter(member => !member.member.roles.cache.hasAny(...roles)).map((value) => value.member)
const channelMembers = newEvent.channel?.members.filter(member => !member.roles.cache.hasAny(...roles)).map((value) => value)
const allMembers = eventMembers.concat(channelMembers ?? [])
const members: GuildMember[] = []
for(const member of allMembers){
if(!members.find(x => x.id == member.id))
for (const member of allMembers) {
if (!members.find(x => x.id == member.id))
members.push(member)
}
if (newEvent.status === GuildScheduledEventStatus.Active)
kenobi marked this conversation as resolved Outdated

check for presence of newEvent.guild at the start of the execute function.
That way we can also log the absence if it occurs.

check for presence of `newEvent.guild` at the start of the `execute` function. That way we can also log the absence if it occurs.
createJFUsers(members, newEvent.name, requestId)
else {
const announcements = (await client.getAnnouncementChannelForGuild(newEvent.guild!.id).messages.fetch()).filter(message => !message.pinned)
const announcementChannel = await client.getAnnouncementChannelForGuild(newEvent.guild.id)
if(!announcementChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: newEvent.guild.id, requestId })
return
}
const announcements = (await announcementChannel.messages.fetch()).filter(message => !message.pinned)
announcements.forEach(message => message.delete())
members.forEach(member => {
member.createDM().then(channel => channel.send(`Die Watchparty ist vorbei, dein Account wurde wieder gelöscht. Wenn du einen permanenten Account haben möchtest, melde dich bei Samantha oder Marukus.`))

View File

@ -1,4 +0,0 @@
export const name = 'ready'
export function execute(client: any) {
//console.log(`Processing ready: ${JSON.stringify(client)} has been created.`)
}

View File

@ -18,8 +18,8 @@ export async function execute(oldState: VoiceState, newState: VoiceState) {
}
const scheduledEvents = (await newState.guild.scheduledEvents.fetch())
.filter((key, value) => key.description?.toLowerCase().includes("!wp") && key.isActive())
.map((key, value) => key)
.filter((key) => key.description?.toLowerCase().includes("!wp") && key.isActive())
.map((key) => key)
const scheduledEventUsers = (await Promise.all(scheduledEvents.map(event => event.fetchSubscribers({withMember: true}))))

View File

@ -16,7 +16,7 @@ export function filterRolesFromMemberUpdate(oldMember: GuildMember, newMember: G
return { addedRoles, removedRoles }
}
export function getGuildSpecificTriggerRoleId(guildId: string): Collection<string, PermissionLevel> {
export function getGuildSpecificTriggerRoleId(): Collection<string, PermissionLevel> {
const outVal = new Collection<string, PermissionLevel>()
outVal.set(config.bot.watcher_role, "VIEWER")
outVal.set(config.bot.jf_admin_role, "ADMIN")

View File

@ -2,7 +2,7 @@ import { GuildMember } from "discord.js";
import { Config } from "../configuration";
import { Maybe, PermissionLevel } from "../interfaces";
import { logger } from "../logger";
import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, GetMovieRemoteSearchResultsOperationRequest, ItemLookupApi, ItemsApi, LibraryApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
import { CreateUserByNameOperationRequest, DeleteUserRequest, GetItemsRequest, ItemsApi, SystemApi, UpdateUserPasswordOperationRequest, UpdateUserPolicyOperationRequest, UserApi } from "./apis";
import { BaseItemDto, UpdateUserPasswordRequest } from "./models";
import { UserDto } from "./models/UserDto";
import { Configuration, ConfigurationParameters } from "./runtime";
@ -56,10 +56,6 @@ export class JellyfinHandler {
return `${discordUser.displayName}${level == "TEMPORARY" ? "_tmp" : ""}`
}
public async addPermissionsToUserAccount(jfUserAccount: UserDto, guildId: string, requestId: string): Promise<UserDto> {
throw new Error("Method not implemented.");
}
private generatePasswordForUser(): string {
return (Math.random() * 10000 + 10000).toFixed(0)
}
@ -253,7 +249,7 @@ export class JellyfinHandler {
}
const movies: BaseItemDto[] = []
for (let i = 0; i < count; i++) {
const index = Math.random() * allMovies.length
const index = Math.floor(Math.random() * allMovies.length)
movies.push(...allMovies.splice(index, 1)) // maybe out of bounds? ?
}

View File

@ -1,13 +1,13 @@
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, GatewayIntentBits, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
import { CommandType } from "../types/commandTypes";
import fs from 'fs'
import { config } from "../configuration";
import { logger } from "../logger";
import { JellyfinHandler } from "../jellyfin/handler";
import { ApplicationCommandDataResolvable, Client, ClientOptions, Collection, Guild, IntentsBitField, Snowflake, TextChannel } from "discord.js";
import fs from 'fs';
import { ScheduledTask, schedule } from "node-cron";
import { v4 as uuid } from 'uuid';
import { manageAnnouncementRoles } from "../commands/announce";
import { v4 as uuid } from 'uuid'
import { task } from "../events/guildScheduledEventCreate";
import { config } from "../configuration";
import { Maybe } from "../interfaces";
import { JellyfinHandler } from "../jellyfin/handler";
import { logger } from "../logger";
import { CommandType } from "../types/commandTypes";
@ -82,8 +82,8 @@ export class ExtendedClient extends Client {
private async cacheAnnouncementServer(guilds: Collection<Snowflake, Guild>) {
for (const guild of guilds.values()) {
const channels: TextChannel[] = <TextChannel[]>(await guild.channels.fetch())
?.filter(channel => channel!.id === config.bot.announcement_channel_id)
.map((value, _) => value)
?.filter(channel => channel?.id === config.bot.announcement_channel_id)
.map((value) => value)
if (!channels || channels.length != 1) {
logger.error(`Could not find announcement channel for guild ${guild.name} with guildId ${guild.id}. Found ${channels}`)
@ -93,8 +93,8 @@ export class ExtendedClient extends Client {
this.announcementChannels.set(guild.id, channels[0])
}
}
public getAnnouncementChannelForGuild(guildId: string): TextChannel {
return this.announcementChannels.get(guildId)! //we set the channel by ourselves only if we find one, I think this is sage (mark my words)
public getAnnouncementChannelForGuild(guildId: string): Maybe<TextChannel> {
return this.announcementChannels.get(guildId)
}
public async cacheUsers(guilds: Collection<Snowflake, Guild>) {
guilds.forEach((guild: Guild, id: Snowflake) => {
@ -127,18 +127,28 @@ export class ExtendedClient extends Client {
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)
const textChannel: Maybe<TextChannel> = this.getAnnouncementChannelForGuild(guild.id)
if(!textChannel) {
logger.error("Could not find announcement channel. Aborting", { guildId: guild.id })
return
}
this.announcementRoleHandlerTask.set(guild.id, schedule("*/10 * * * * *", async () => {
const requestId = uuid()
const messages = (await textChannel.messages.fetchPinned()).filter(message => message.cleanContent.includes("[initial]"))
kenobi marked this conversation as resolved
Review

Unable to continue, should result in a return.

Unable to continue, should result in a return.
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 })
return
kenobi marked this conversation as resolved Outdated
const message = await messages.at(0)?.fetch()
if (!message) {
  logger.error(`No pinned message found`,{requestId,guildId})
  return
}
``` const message = await messages.at(0)?.fetch() if (!message) { logger.error(`No pinned message found`,{requestId,guildId}) return } ```
} else if (messages.size == 0) {
logger.error("Could not find any pinned announcement messages. Unable to manage roles!", { guildId: guild.id, requestId })
return
}
const message = await messages.at(0)!.fetch()
const message = await messages.at(0)?.fetch()
if (!message) {
logger.error(`No pinned message found`, { guildId: guild.id, requestId })
return
}
//logger.debug(`Message: ${JSON.stringify(message, null, 2)}`, { guildId: guild.id, requestId })
const reactions = message.reactions.resolve("🎫")
@ -152,9 +162,8 @@ export class ExtendedClient extends Client {
}
}
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)
public stopAnnouncementRoleBackgroundTask(guildId: string, requestId: string) {
const task: Maybe<ScheduledTask> = this.announcementRoleHandlerTask.get(guildId)
if (!task) {
logger.error(`No task found for guildID ${guildId}.`, { guildId, requestId })
return